import logging
import os
import sys
import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import matplotlib

Python
import logging: Импортирует модуль logging для записи событий и отладочной информации.

import os: Импортирует модуль os для взаимодействия с операционной системой, например, создания каталогов.

import sys: Импортирует модуль sys, который предоставляет доступ к некоторым переменным и функциям, используемым интерпретатором Python.

import MetaTrader5 as mt5: Импортирует модуль MetaTrader5 для взаимодействия с платформой MetaTrader 5 (обычно для получения данных о торговле). Переименован в mt5 для краткости.

import pandas as pd: Импортирует библиотеку pandas для работы с табличными данными. Переименован в pd для краткости.

import numpy as np: Импортирует библиотеку numpy для работы с массивами и математическими операциями. Переименован в np для краткости.

import matplotlib: Импортирует модуль matplotlib, основную библиотеку для построения графиков.

# Use a non-interactive backend for matplotlib
matplotlib.use('Agg')

Python
matplotlib.use('Agg'): Устанавливает неинтерактивный бэкенд Agg для matplotlib. Это позволяет сохранять графики в файлы без необходимости отображения на экране.

import matplotlib.pyplot as plt
import time
import mplfinance as mpf  # For candlestick charts
from datetime import datetime
from deap import base, creator, tools, algorithms
import random
import warnings
from multiprocessing import cpu_count
from functools import partial
import gc

Python
import matplotlib.pyplot as plt: Импортирует модуль pyplot из matplotlib для создания графиков. Переименован в plt для краткости.

import time: Импортирует модуль time для работы со временем.

import mplfinance as mpf: Импортирует библиотеку mplfinance для построения графиков свечей (candlestick). Переименован в mpf для краткости.

from datetime import datetime: Импортирует класс datetime из модуля datetime для работы с датами и временем.

from deap import base, creator, tools, algorithms: Импортирует классы и функции для использования генетических алгоритмов из библиотеки deap.

import random: Импортирует модуль random для генерации случайных чисел.

import warnings: Импортирует модуль warnings для управления предупреждениями.

from multiprocessing import cpu_count: Импортирует функцию cpu_count из модуля multiprocessing для получения количества доступных ядер процессора.

from functools import partial: Импортирует функцию partial из модуля functools для создания функций с предопределенными аргументами.

import gc: Импортирует модуль gc для сборки мусора.

import xgboost as xgb
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import cross_val_score, train_test_split, StratifiedKFold
from sklearn.metrics import (
    mean_squared_error,
    mean_absolute_error,
    r2_score,
    mean_absolute_percentage_error,
    classification_report,
    roc_auc_score
)

Python
import xgboost as xgb: Импортирует библиотеку xgboost для градиентного бустинга. Переименован в xgb для краткости.

from sklearn.preprocessing import MinMaxScaler: Импортирует класс MinMaxScaler из sklearn для масштабирования признаков.

from sklearn.model_selection import cross_val_score, train_test_split, StratifiedKFold: Импортирует функции для кросс-валидации, разделения данных на обучающую и тестовую выборки, а также стратифицированной кросс-валидации из библиотеки sklearn.

from sklearn.metrics import ( ... ): Импортирует метрики оценки моделей из sklearn:

mean_squared_error: Среднеквадратичная ошибка.

mean_absolute_error: Средняя абсолютная ошибка.

r2_score: Коэффициент детерминации (R-квадрат).

mean_absolute_percentage_error: Средняя абсолютная процентная ошибка.

classification_report: Отчёт о классификации.

roc_auc_score: ROC AUC score.

from ta.volatility import AverageTrueRange, BollingerBands
from ta.momentum import RSIIndicator, StochasticOscillator, WilliamsRIndicator, ROCIndicator
from ta.trend import MACD, EMAIndicator, CCIIndicator, ADXIndicator, IchimokuIndicator

Python
from ta.volatility import ...: Импортирует классы для расчёта индикаторов волатильности из библиотеки ta:

AverageTrueRange: Средний истинный диапазон.

BollingerBands: Полосы Боллинджера.

from ta.momentum import ...: Импортирует классы для расчёта индикаторов моментума из библиотеки ta:

RSIIndicator: Индикатор относительной силы (RSI).

StochasticOscillator: Стохастический осциллятор.

WilliamsRIndicator: Индикатор Вильямса %R.

ROCIndicator: Индикатор скорости изменения цены (ROC).

from ta.trend import ...: Импортирует классы для расчёта индикаторов тренда из библиотеки ta:

MACD: Схождение-расхождение скользящих средних (MACD).

EMAIndicator: Экспоненциальная скользящая средняя (EMA).

CCIIndicator: Индекс товарного канала (CCI).

ADXIndicator: Индекс среднего направления движения (ADX).

IchimokuIndicator: Индикатор Ишимоку.

from sklearn.ensemble import StackingRegressor, RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import LinearRegression
import joblib
import seaborn as sns
import shap
from statsmodels.tsa.stattools import adfuller
from tabulate import tabulate

Python
from sklearn.ensemble import ...: Импортирует классы для ансамблевых моделей из sklearn:

StackingRegressor: Регрессор стекинга.

RandomForestRegressor: Регрессор случайного леса.

GradientBoostingRegressor: Регрессор градиентного бустинга.

from sklearn.linear_model import LinearRegression: Импортирует класс LinearRegression для линейной регрессии.

import joblib: Импортирует библиотеку joblib для сохранения и загрузки моделей.

import seaborn as sns: Импортирует библиотеку seaborn для создания статистических графиков.

import shap: Импортирует библиотеку shap для интерпретации результатов машинного обучения.

from statsmodels.tsa.stattools import adfuller: Импортирует функцию adfuller из библиотеки statsmodels для теста Дики-Фуллера (теста на стационарность временных рядов).

from tabulate import tabulate: Импортирует функцию tabulate из библиотеки tabulate для создания таблиц в текстовом формате.

warnings.filterwarnings('ignore')

Python
warnings.filterwarnings('ignore'): Отключает отображение предупреждений, что может быть полезно для предотвращения засорения вывода.

# --------------------- Logging Configuration ---------------------
class ExcludeInfoFilter(logging.Filter):
    def filter(self, record):
        exclude_keywords = ['Classification Report', 'ROC AUC Score']
        return not any(keyword in record.getMessage() for keyword in exclude_keywords)

Python
class ExcludeInfoFilter(logging.Filter):: Определяется класс ExcludeInfoFilter, который наследуется от logging.Filter.

def filter(self, record):: Определяется метод filter внутри класса, который будет фильтровать записи журнала.

exclude_keywords = ['Classification Report', 'ROC AUC Score']: Список ключевых слов, по которым будет производиться фильтрация.

return not any(keyword in record.getMessage() for keyword in exclude_keywords): Возвращает True, если сообщение журнала не содержит ни одного из ключевых слов, и False в противном случае, эффективно исключая эти записи из журнала.

# Remove existing handlers if any
for handler in logging.root.handlers[:]:
    logging.root.removeHandler(handler)

Python
for handler in logging.root.handlers[:]: Итерируется по копии списка обработчиков журнала logging.root, чтобы избежать проблем при изменении списка в процессе итерации.

logging.root.removeHandler(handler): Удаляет каждый обработчик журнала из корня, чтобы начать с чистой конфигурации.

# Create logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)  # Capture all logs

Python
logger = logging.getLogger(): Создает экземпляр объекта логгера.

logger.setLevel(logging.DEBUG): Устанавливает уровень журнала на DEBUG, что означает, что все сообщения уровня DEBUG и выше будут записаны.

# Create FileHandler
file_handler = logging.FileHandler("strategy_optimization.log")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))

Python
file_handler = logging.FileHandler("strategy_optimization.log"): Создает обработчик журнала FileHandler, который будет записывать сообщения в файл strategy_optimization.log.

file_handler.setLevel(logging.DEBUG): Устанавливает уровень обработчика файла на DEBUG, что соответствует уровню логгера.

file_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')): Устанавливает формат сообщений журнала, который включает:

%(asctime)s: Время создания записи.

%(levelname)s: Уровень сообщения (DEBUG, INFO, WARNING, ERROR, CRITICAL).

%(message)s: Само сообщение.

# Create StreamHandler
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.INFO)
stream_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
stream_handler.addFilter(ExcludeInfoFilter())

Python
stream_handler = logging.StreamHandler(sys.stdout): Создает обработчик журнала StreamHandler, который будет выводить сообщения в стандартный вывод (консоль).

stream_handler.setLevel(logging.INFO): Устанавливает уровень обработчика консоли на INFO, что означает, что только сообщения уровня INFO и выше будут выведены в консоль.

stream_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')): Устанавливает формат сообщений журнала для консоли.

stream_handler.addFilter(ExcludeInfoFilter()): Добавляет фильтр ExcludeInfoFilter к обработчику консоли, чтобы не выводить сообщения, содержащие 'Classification Report' и 'ROC AUC Score' в консоль.

logger.addHandler(file_handler)
logger.addHandler(stream_handler)

Python
logger.addHandler(file_handler): Добавляет file_handler к логгеру.

logger.addHandler(stream_handler): Добавляет stream_handler к логгеру.

# --------------------- Parameters and Global Variables ---------------------
start_date = datetime(2015, 1, 1)
end_date = datetime.today()
split_date = datetime(2021, 1, 1)

backtest_start_date = datetime(2022, 1, 1)
backtest_end_date = datetime.today()

Python
start_date = datetime(2015, 1, 1): Устанавливает начальную дату для загрузки данных.

end_date = datetime.today(): Устанавливает конечную дату для загрузки данных (сегодняшний день).

split_date = datetime(2021, 1, 1): Устанавливает дату разделения данных на обучающую и тестовую выборки.

backtest_start_date = datetime(2022, 1, 1): Устанавливает начальную дату для бэктестинга.

backtest_end_date = datetime.today(): Устанавливает конечную дату для бэктестинга.

transaction_cost = 0.0002
slippage = 0.0001
risk_free_rate = 0.02 / 252
time_steps = 30

Python
transaction_cost = 0.0002: Устанавливает стоимость транзакции (комиссия) как 0.02%.

slippage = 0.0001: Устанавливает проскальзывание как 0.01%.

risk_free_rate = 0.02 / 252: Устанавливает годовую безрисковую ставку как 0.02, разделённую на 252 (количество торговых дней в году).

time_steps = 30: Задает количество временных шагов (вероятно, используется для какого-то расчета).

timeframes_to_process = [
    mt5.TIMEFRAME_D1,
]

Python
timeframes_to_process = [mt5.TIMEFRAME_D1]: Список таймфреймов для обработки. Здесь указан только дневной таймфрейм mt5.TIMEFRAME_D1.

show_plots = True
my_directory = '1BBa'
plot_base_dir = "plots"
current_date = datetime.today().strftime('%Y-%m-%d')
num_cores_to_use = 6
currency_pairs = ["AUDUSD"]

Python
show_plots = True: Флаг, указывающий на необходимость отображения графиков.

my_directory = '1BBa': Имя каталога, который будет использоваться для сохранения результатов.

plot_base_dir = "plots": Базовый каталог для сохранения графиков.

current_date = datetime.today().strftime('%Y-%m-%d'): Текущая дата в формате YYYY-MM-DD для создания подкаталогов.

num_cores_to_use = 6: Количество ядер процессора, которые будут использоваться для параллельных вычислений.

currency_pairs = ["AUDUSD"]: Список валютных пар, которые будут анализироваться. Здесь только пара AUDUSD.

timeframes_dict = {
    mt5.TIMEFRAME_M1: "M1",
    mt5.TIMEFRAME_M2: "M2",
    mt5.TIMEFRAME_M3: "M3",
    mt5.TIMEFRAME_M4: "M4",
    mt5.TIMEFRAME_M5: "M5",
    mt5.TIMEFRAME_M6: "M6",
    mt5.TIMEFRAME_M10: "M10",
    mt5.TIMEFRAME_M12: "M12",
    mt5.TIMEFRAME_M15: "M15",
    mt5.TIMEFRAME_M20: "M20",
    mt5.TIMEFRAME_M30: "M30",
    mt5.TIMEFRAME_H1: "H1",
    mt5.TIMEFRAME_H2: "H2",
    mt5.TIMEFRAME_H3: "H3",
    mt5.TIMEFRAME_H4: "H4",
    mt5.TIMEFRAME_H6: "H6",
    mt5.TIMEFRAME_H8: "H8",
    mt5.TIMEFRAME_H12: "H12",
    mt5.TIMEFRAME_D1: "D1",
    mt5.TIMEFRAME_W1: "W1",
    mt5.TIMEFRAME_MN1: "MN1",
}

Python
timeframes_dict = { ... }: Словарь, сопоставляющий константы таймфреймов из MetaTrader5 с их строковыми представлениями.

ga_base_seed = 41
ga_pop_size = 150
ga_num_generations = 10
ga_cxpb = 0.7
ga_mutpb = 0.2
ga_mutate_indpb = 0.3
ga_mate_indpb = 0.9

Python
ga_base_seed = 41: Базовое случайное зерно для генетического алгоритма.

ga_pop_size = 150: Размер популяции для генетического алгоритма.

ga_num_generations = 10: Количество поколений для генетического алгоритма.

ga_cxpb = 0.7: Вероятность скрещивания (кроссовера) для генетического алгоритма.

ga_mutpb = 0.2: Вероятность мутации для генетического алгоритма.

ga_mutate_indpb = 0.3: Вероятность мутации одного гена в особи для генетического алгоритма.

ga_mate_indpb = 0.9: Вероятность обмена генами во время скрещивания для генетического алгоритма.

xgb_n_estimators = 200
xgb_max_depth = 20
xgb_learning_rate = 0.1
xgb_random_state = ga_base_seed

Python
xgb_n_estimators = 200: Количество деревьев в XGBoost.

xgb_max_depth = 20: Максимальная глубина дерева в XGBoost.

xgb_learning_rate = 0.1: Скорость обучения для XGBoost.

xgb_random_state = ga_base_seed: Случайное зерно для XGBoost.

# Initialize DEAP framework
creator.create('FitnessMax', base.Fitness, weights=(1.0,))
creator.create('Individual', list, fitness=creator.FitnessMax)

Python
creator.create('FitnessMax', base.Fitness, weights=(1.0,)): Создает класс FitnessMax для представления фитнеса (максимизация) с весом 1.0, используя функционал deap.

creator.create('Individual', list, fitness=creator.FitnessMax): Создает класс Individual, который является списком, с ассоциированным фитнесом типа FitnessMax, используя функционал deap.

sl_features = [
    'RSI', 'MACD', 'ATR', 'BB_High', 'BB_Low', 'EMA_14', 'SMA_14',
    'DayOfWeek', 'WeekOfYear', 'Month', 'ROC_14', 'Momentum_14',
    'WilliamsR', 'Ichimoku_A', 'Ichimoku_B', 'Corr_Close_RSI_14',
    'Return_Lag_1', 'Return_Lag_2', 'Return_Lag_3', 'RSI_MACD', 'ATR_Close',
    'Close_Stationary', 'High_Stationary', 'Low_Stationary'
]

Python
sl_features = [ ... ]: Список фичей, используемых в модели прогнозирования Stop Loss (SL).

window_values_ga = np.arange(20, 252, 1)
multiplier_values_ga = np.arange(0.8, 3.0, 0.1)
tp_factor_values_ga = np.arange(1.2, 5.61, 0.05)
sl_factor_values_ga = np.arange(0.1, 1.21, 0.05)

sl_max_depth_values = [3, 5, 7]
sl_learning_rate_values = [0.01, 0.05, 0.1]
sl_n_estimators_values = [100, 200, 300]

Python
window_values_ga = np.arange(20, 252, 1): Диапазон значений для окна (window) генетического алгоритма, созданный с помощью numpy.arange.

multiplier_values_ga = np.arange(0.8, 3.0, 0.1): Диапазон значений для мультипликатора генетического алгоритма.

tp_factor_values_ga = np.arange(1.2, 5.61, 0.05): Диапазон значений для фактора тейк-профита (take-profit) генетического алгоритма.

sl_factor_values_ga = np.arange(0.1, 1.21, 0.05): Диапазон значений для фактора стоп-лосса (stop-loss) генетического алгоритма.

sl_max_depth_values = [3, 5, 7]: Список возможных значений для максимальной глубины дерева в модели stop-loss.

sl_learning_rate_values = [0.01, 0.05, 0.1]: Список возможных значений для скорости обучения в модели stop-loss.

sl_n_estimators_values = [100, 200, 300]: Список возможных значений для количества деревьев в модели stop-loss.

window_values_ga_list = window_values_ga.tolist()
multiplier_values_ga_list = multiplier_values_ga.tolist()
tp_factor_values_ga_list = tp_factor_values_ga.tolist()
sl_factor_values_ga_list = sl_factor_values_ga.tolist()

sl_max_depth_list = sl_max_depth_values
sl_learning_rate_list = sl_learning_rate_values
sl_n_estimators_list = sl_n_estimators_values

Python
Преобразует numpy массивы в списки для удобства использования в генетическом алгоритме.

scaler_X_selected_dict = {}
pip_scale_factor = 100000

Python
scaler_X_selected_dict = {}: Словарь для хранения Scaler'ов, используемых для масштабирования данных.

pip_scale_factor = 100000: Масштабирующий коэффициент для пипсов, значение по умолчанию 100000.

# --------------------- Helper Functions ---------------------
def get_conversion_rate(from_cur, to_cur):
    direct_symbol = to_cur + from_cur
    reverse_symbol = from_cur + to_cur

    symbol_info_direct = mt5.symbol_info(direct_symbol)
    if symbol_info_direct is not None:
        mt5.symbol_select(direct_symbol, True)
        tick = mt5.symbol_info_tick(direct_symbol)
        if tick is not None:
            if direct_symbol.startswith(to_cur):
                rate = 1.0 / tick.ask
                return rate
            else:
                return tick.ask

    symbol_info_reverse = mt5.symbol_info(reverse_symbol)
    if symbol_info_reverse is not None:
        mt5.symbol_select(reverse_symbol, True)
        tick = mt5.symbol_info_tick(reverse_symbol)
        if tick is not None:
            if reverse_symbol.startswith(to_cur):
                rate = 1.0 / tick.ask
                return rate
            else:
                return tick.ask

    return None

Python
def get_conversion_rate(from_cur, to_cur):: Определяет функцию для получения обменного курса между двумя валютами.

direct_symbol = to_cur + from_cur: Строит прямое название символа (например, EURUSD).

reverse_symbol = from_cur + to_cur: Строит обратное название символа (например, USDEUR).

symbol_info_direct = mt5.symbol_info(direct_symbol): Получает информацию о символе из MetaTrader 5 для прямого символа.

Далее следует блок if для проверки, что информация получена, символ выбран и получена цена тика. Если да, то функция возвращает соответствующий курс.

Аналогичный блок if для обратного символа.

return None: Если курс не найден, функция возвращает None.

def apply_transformations(series):
    transformations = {}
    transformations['first_diff'] = series.diff().fillna(0)
    transformations['pct_change'] = series.pct_change().fillna(0)
    transformations['log'] = np.log(series.replace(0, np.nan)).fillna(0)
    return transformations

Python
def apply_transformations(series):: Определяет функцию для применения различных преобразований к временному ряду.

transformations = {}: Инициализирует пустой словарь для хранения преобразованных рядов.

transformations['first_diff'] = series.diff().fillna(0): Вычисляет первую разность ряда и заполняет NaN значения нулями.

transformations['pct_change'] = series.pct_change().fillna(0): Вычисляет процентное изменение ряда и заполняет NaN значения нулями.

transformations['log'] = np.log(series.replace(0, np.nan)).fillna(0): Вычисляет логарифм ряда (с заменой 0 на NaN, чтобы избежать ошибки, и заполняет NaN значения нулями).

return transformations: Возвращает словарь с преобразованными рядами.

def replace_inf_nan(series):
    series = series.replace([np.inf, -np.inf], np.nan)
    series = series.fillna(method='ffill').fillna(method='bfill')
    return series

Python
def replace_inf_nan(series):: Определяет функцию для замены бесконечностей и NaN значений в ряде.

series = series.replace([np.inf, -np.inf], np.nan): Заменяет бесконечности на NaN.

series = series.fillna(method='ffill').fillna(method='bfill'): Заполняет NaN значения методом ffill (заполнение последним валидным значением) и затем bfill (заполнение следующим валидным значением).

return series: Возвращает обработанный ряд.

def select_best_transformation(series, transformations):
    adf_results = {}
    for name, transformed_series in transformations.items():
        transformed_series_clean = transformed_series.dropna()
        if len(transformed_series_clean) < 10:
            adf_results[name] = 1.0
            continue
        result = adfuller(transformed_series_clean, autolag='AIC')
        adf_results[name] = result[1]

    stationary_transforms = {k: v for k, v in adf_results.items() if v < 0.05}
    if not stationary_transforms:
        logging.warning("No stationary transformation found. Using first_diff as default.")
        best_transform = 'first_diff'
    else:
        best_transform = min(stationary_transforms, key=stationary_transforms.get)

    best_series = transformations[best_transform]
    return best_transform, best_series

Python
def select_best_transformation(series, transformations):: Определяет функцию для выбора лучшего преобразования, которое приводит временной ряд к стационарному.

adf_results = {}: Создаёт пустой словарь для хранения результатов теста Дики-Фуллера.

for name, transformed_series in transformations.items():: Итерируется по всем преобразованиям.

transformed_series_clean = transformed_series.dropna(): Очищает преобразованный ряд от NaN значений.

if len(transformed_series_clean) < 10:: Если длина ряда меньше 10, то результат adf записывается в словарь как 1.0, переходим к следующему преобразованию.

result = adfuller(transformed_series_clean, autolag='AIC'): Выполняет тест Дики-Фуллера на стационарность, используя параметр autolag='AIC' для автоматического выбора лага.

adf_results[name] = result[1]: Записывает p-value из результатов теста в словарь.

stationary_transforms = {k: v for k, v in adf_results.items() if v < 0.05}: Создаёт словарь только стационарных преобразований, где p-value меньше 0.05.

if not stationary_transforms:: Если нет стационарных преобразований, то выбирается первая разность first_diff как значение по умолчанию.

else: best_transform = min(stationary_transforms, key=stationary_transforms.get): В противном случае выбирается преобразование с минимальным p-value.

best_series = transformations[best_transform]: Получает преобразованный ряд.

return best_transform, best_series: Возвращает название лучшего преобразования и сам ряд.

def get_mt5_data(symbol, timeframe, start, end):
    timeframe_name = timeframes_dict.get(timeframe, 'UNKNOWN')
    file_name = f"{symbol}_{timeframe_name}_{start.date()}_to_{end.date()}.csv"
    if os.path.exists(file_name):
        logging.info(f"Loading data for {symbol} from {file_name}...")
        try:
            data = pd.read_csv(file_name, index_col='Date', parse_dates=True)
            if 'Close' not in data.columns and symbol not in data.columns:
                logging.error(f"Columns 'Close' or '{symbol}' not found in CSV file {file_name}")
                return pd.DataFrame()
            return data
        except Exception as e:
            logging.error(f"Error reading CSV file {file_name}: {e}")
            return pd.DataFrame()
    else:
        logging.info(f"Loading data for {symbol} from MT5...")
        try:
            rates = mt5.copy_rates_range(symbol, timeframe, start, end)
            if rates is None or len(rates) == 0:
                logging.error(f"No data for symbol {symbol}.")
                return pd.DataFrame()
            data = pd.DataFrame(rates)
            data['time'] = pd.to_datetime(data['time'], unit='s')
            data.rename(columns={
                "time": "Date",
                "open": "Open",
                "high": "High",
                "low": "Low",
                "close": "Close",
                "tick_volume": "Volume",
                "spread": "Spread",
                "real_volume": "RealVolume"
            }, inplace=True)
            data.set_index('Date', inplace=True)

            return data
        except Exception as e:
            logging.error(f"Error loading data for {symbol}: {e}")
            return pd.DataFrame()

Python
def get_mt5_data(symbol, timeframe, start, end):: Определяет функцию для загрузки данных из MT5 или CSV-файла.

timeframe_name = timeframes_dict.get(timeframe, 'UNKNOWN'): Получает имя таймфрейма из словаря, если его нет, то возвращает 'UNKNOWN'.

file_name = f"{symbol}_{timeframe_name}_{start.date()}_to_{end.date()}.csv": Формирует имя файла CSV для сохранения/загрузки данных.

if os.path.exists(file_name):: Проверяет, существует ли файл, если да то:

logging.info(f"Loading data for {symbol} from {file_name}..."): Выводит сообщение о загрузке из файла.

try ... except Exception as e:: Обрабатывает возможные ошибки.

data = pd.read_csv(file_name, index_col='Date', parse_dates=True): Загружает данные из CSV-файла в DataFrame, устанавливая столбец 'Date' как индекс и парся его как даты.

59.0s
Продолжайте отвечать, включая последнюю строку ответа.

Хорошо, продолжаем разбор кода с последней строки функции get_mt5_data:

if 'Close' not in data.columns and symbol not in data.columns:
                logging.error(f"Columns 'Close' or '{symbol}' not found in CSV file {file_name}")
                return pd.DataFrame()
            return data
        except Exception as e:
            logging.error(f"Error reading CSV file {file_name}: {e}")
            return pd.DataFrame()
    else:
        logging.info(f"Loading data for {symbol} from MT5...")
        try:
            rates = mt5.copy_rates_range(symbol, timeframe, start, end)
            if rates is None or len(rates) == 0:
                logging.error(f"No data for symbol {symbol}.")
                return pd.DataFrame()
            data = pd.DataFrame(rates)
            data['time'] = pd.to_datetime(data['time'], unit='s')
            data.rename(columns={
                "time": "Date",
                "open": "Open",
                "high": "High",
                "low": "Low",
                "close": "Close",
                "tick_volume": "Volume",
                "spread": "Spread",
                "real_volume": "RealVolume"
            }, inplace=True)
            data.set_index('Date', inplace=True)

            return data
        except Exception as e:
            logging.error(f"Error loading data for {symbol}: {e}")
            return pd.DataFrame()

Python
if 'Close' not in data.columns and symbol not in data.columns:: Проверяет, есть ли в загруженных из CSV данных столбцы 'Close' или символ валютной пары. Если нет, то выводится ошибка и возвращается пустой DataFrame.

return data: Если данные загружены успешно из CSV, то возвращается DataFrame.

except Exception as e:: Перехватывает все исключения, возникшие при чтении из CSV.

logging.error(f"Error reading CSV file {file_name}: {e}"): Выводит сообщение об ошибке в лог.

return pd.DataFrame(): Возвращает пустой DataFrame в случае ошибки.

else:: Если файл не существует (блок if os.path.exists(file_name) вернул False):

logging.info(f"Loading data for {symbol} from MT5..."): Выводит сообщение о загрузке данных из MT5.

try ... except Exception as e:: Обрабатывает возможные ошибки при загрузке из MT5.

rates = mt5.copy_rates_range(symbol, timeframe, start, end): Получает котировки из MT5 в заданном диапазоне дат и таймфрейме.

if rates is None or len(rates) == 0:: Проверяет, что данные действительно получены, иначе выводится сообщение об ошибке и возвращается пустой DataFrame.

data = pd.DataFrame(rates): Преобразует полученные из MT5 данные в DataFrame.

data['time'] = pd.to_datetime(data['time'], unit='s'): Преобразует столбец времени в формат даты и времени, принимая время как число секунд с начала эпохи.

data.rename(columns={ ... }, inplace=True): Переименовывает столбцы DataFrame в более понятные названия.

data.set_index('Date', inplace=True): Устанавливает столбец 'Date' в качестве индекса DataFrame.

return data: Возвращает DataFrame с данными, полученными из MT5.

except Exception as e:: Перехватывает все исключения, возникшие при получении данных из MT5.

logging.error(f"Error loading data for {symbol}: {e}"): Выводит сообщение об ошибке в лог.

return pd.DataFrame(): Возвращает пустой DataFrame в случае ошибки.

# --------------------- StrategyEvaluator Class ---------------------
class StrategyEvaluator:
    def __init__(self, symbol, df, train, models, scaler_y_high, scaler_y_low, scaler_y_close,
                 prediction_columns=['Ensemble_Predicted_Close_Future']):
        self.symbol = symbol
        self.df = df
        self.train = train
        self.models = models
        self.scaler_y_high = scaler_y_high
        self.scaler_y_low = scaler_y_low
        self.scaler_y_close = scaler_y_close
        self.prediction_columns = prediction_columns
        self.trades_dict = {col: [] for col in prediction_columns}

        self.symbol_info = mt5.symbol_info(self.symbol)
        if self.symbol_info is None:
            logging.error(f"Cannot retrieve symbol info for {self.symbol}. Using default digits=5.")
            self.point = 0.00001
            self.spread = 0
            self.volume_min = 0.01
            self.volume_max = 500.0
            self.volume_step = 0.01
            self.swap_long = 0.0
            self.swap_short = 0.0
            self.trade_tick_value = 0.0
            self.trade_tick_value_profit = 0.0
            self.trade_tick_value_loss = 0.0
            self.contract_size = 100000.0
            self.leverage = 30.0
            self.deposit_currency = "EUR"
            self.currency_margin = "EUR"
        else:
            self.point = self.symbol_info.point
            self.spread = self.symbol_info.spread * self.point
            self.volume_min = self.symbol_info.volume_min
            self.volume_max = self.symbol_info.volume_max
            self.volume_step = self.symbol_info.volume_step
            self.swap_long = self.symbol_info.swap_long
            self.swap_short = self.symbol_info.swap_short
            self.trade_tick_value = self.symbol_info.trade_tick_value
            self.trade_tick_value_profit = self.symbol_info.trade_tick_value_profit
            self.trade_tick_value_loss = self.symbol_info.trade_tick_value_loss
            self.contract_size = self.symbol_info.trade_contract_size
            self.leverage = 30.0
            self.deposit_currency = "EUR"
            self.currency_margin = self.symbol_info.currency_margin

            global pip_scale_factor
            if self.symbol_info.digits:
                digits = self.symbol_info.digits
                pip_scale_factor = 10 ** digits
                point_in_string = f"{self.point:f}"
                point_in_string = point_in_string.rstrip('0')
                if point_in_string.endswith('.'):
                    point_in_string += '0'
                logging.info(f"Using {self.symbol} digits = {digits}. Point: {point_in_string}")

        if self.deposit_currency == "EUR" and self.currency_margin != "EUR":
            conversion_rate = get_conversion_rate(self.currency_margin, self.deposit_currency)
            if conversion_rate is None:
                logging.warning(
                    f"Cannot find conversion rate from {self.currency_margin} to {self.deposit_currency}. Using 1.0 as fallback.")
                self.currency_conversion_rate = 1.0
            else:
                self.currency_conversion_rate = conversion_rate
                logging.info(
                    f"Conversion rate from {self.currency_margin} to {self.deposit_currency}: {self.currency_conversion_rate}")
        else:
            self.currency_conversion_rate = 1.0

Python
class StrategyEvaluator:: Объявляет класс StrategyEvaluator для оценки торговых стратегий.

def __init__(self, symbol, df, train, models, scaler_y_high, scaler_y_low, scaler_y_close, prediction_columns=['Ensemble_Predicted_Close_Future']):: Конструктор класса StrategyEvaluator.

self.symbol: Символ валютной пары.

self.df: DataFrame со всеми данными.

self.train: DataFrame с данными для обучения.

self.models: Словарь моделей.

self.scaler_y_high, self.scaler_y_low, self.scaler_y_close: Скейлеры для целевых переменных.

self.prediction_columns: Список столбцов с предсказаниями.

self.trades_dict = {col: [] for col in prediction_columns}: Инициализирует словарь для хранения сделок для каждого предсказываемого столбца, каждый список изначально пустой.

self.symbol_info = mt5.symbol_info(self.symbol): Получает информацию о символе из MT5.

if self.symbol_info is None:: Если информацию не удалось получить, то устанавливаются дефолтные значения.

logging.error(f"Cannot retrieve symbol info for {self.symbol}. Using default digits=5."): Выводит сообщение об ошибке.

self.point, self.spread, self.volume_min, self.volume_max, self.volume_step, self.swap_long, self.swap_short, self.trade_tick_value, self.trade_tick_value_profit, self.trade_tick_value_loss, self.contract_size, self.leverage, self.deposit_currency, self.currency_margin устанавливаются в значения по умолчанию.

else:: Если информация получена, то устанавливаются соответствующие значения из self.symbol_info.

self.point, self.spread, self.volume_min, self.volume_max, self.volume_step, self.swap_long, self.swap_short, self.trade_tick_value, self.trade_tick_value_profit, self.trade_tick_value_loss, self.contract_size, self.leverage, self.deposit_currency, self.currency_margin получаются из self.symbol_info.

global pip_scale_factor: Используем глобальную переменную pip_scale_factor.

if self.symbol_info.digits:: Проверяем, есть ли у символа параметр digits.

digits = self.symbol_info.digits: Получаем число знаков после запятой.

pip_scale_factor = 10 ** digits: Устанавливаем коэффициент для пипсов.

Выводит сообщение об используемом количестве знаков после запятой и величине пункта.

if self.deposit_currency == "EUR" and self.currency_margin != "EUR":: Проверяет, нужна ли конвертация валюты, если валюта депозита евро, а валюта маржи другая.

conversion_rate = get_conversion_rate(self.currency_margin, self.deposit_currency): Получает курс конвертации.

if conversion_rate is None:: Если курс не найден, то выводится предупреждение и используется значение по умолчанию 1.0.

else:: Если курс получен успешно, он устанавливается и выводится сообщение в лог.

else: self.currency_conversion_rate = 1.0: Если конвертация не требуется, то устанавливается значение конвертации в 1.0.

def evaluate(self, individual_params, classifier_sl=None, sl_threshold=0.7, backtest_data=None, data_source=None):
        sortino_ratios = []
        try:
            logging.debug(f"Evaluating individual parameters: {individual_params}")
            window, multiplier, tp_factor, sl_factor = map(float, individual_params)
            window = int(window)
            if window >= len(self.df) or window < 10:
                logging.warning(f"Invalid window size: {window}. Assigning minimal fitness.")
                sortino_ratios = [1e-10] * len(self.prediction_columns)
                return tuple(sortino_ratios)

            df_to_use = backtest_data if backtest_data is not None else self.df

            if 'Ask' not in df_to_use.columns or 'Bid' not in df_to_use.columns:
                logging.error("Missing 'Ask' or 'Bid' column in DataFrame.")
                return tuple([1e-10] * len(self.prediction_columns))

            for prediction_column in self.prediction_columns:
                rolling_mean = df_to_use[prediction_column].rolling(window).mean().shift(1).values
                rolling_std = df_to_use[prediction_column].rolling(window).std().shift(1).values
                upper = rolling_mean + multiplier * rolling_std
                lower = rolling_mean - multiplier * rolling_std

                pc_shift2 = df_to_use[prediction_column].shift(2).values
                pc_shift1 = df_to_use[prediction_column].shift(1).values
                pc = df_to_use[prediction_column].values

                condition_buy = (
                        (pc_shift2 < pc_shift1) &
                        (pc_shift1 < pc) &
                        (pc <= upper) &
                        (lower < np.roll(lower, 1))
                )

                condition_sell = (
                        (pc_shift2 > pc_shift1) &
                        (pc_shift1 > pc) &
                        (pc >= lower) &
                        (upper > np.roll(upper, 1))
                )

                signals = np.zeros(len(df_to_use))
                signals[condition_buy] = 1
                signals[condition_sell] = -1

                positions = np.zeros(len(df_to_use))
                open_position = False
                entry_price = 0.0
                take_profit = 0.0
                stop_loss = 0.0
                entry_index = 0
                trade_type = 0
                trades_list = []
                pnl_history = []
                time_in_profit = 0
                time_in_loss = 0

Python
def evaluate(self, individual_params, classifier_sl=None, sl_threshold=0.7, backtest_data=None, data_source=None):: Определяет функцию для оценки торговой стратегии на основе заданных параметров.

individual_params: Список параметров для оценки.

classifier_sl: Классификатор для стоп-лосса (может быть None).

sl_threshold: Порог для классификатора стоп-лосса.

backtest_data: Альтернативный DataFrame для бэктеста (может быть None).

data_source: источник данных

sortino_ratios = []: Список для хранения коэффициентов Сортино.

try ... except Exception as e:: Обработка возможных исключений.

logging.debug(f"Evaluating individual parameters: {individual_params}"): Выводит сообщение с отладочной информацией.

window, multiplier, tp_factor, sl_factor = map(float, individual_params): Распаковывает параметры из individual_params.

window = int(window): Приводит значение окна к целому типу.

if window >= len(self.df) or window < 10:: Проверяет корректность значения окна, если оно некорректное, то выводит предупреждение и присваивает минимальный фитнес.

df_to_use = backtest_data if backtest_data is not None else self.df: Выбирает DataFrame для анализа, используя backtest_data если оно предоставлено, в противном случае - self.df.

if 'Ask' not in df_to_use.columns or 'Bid' not in df_to_use.columns:: Если в DataFrame нет столбцов 'Ask' или 'Bid', выводится сообщение об ошибке и возвращается минимальный фитнес.

for prediction_column in self.prediction_columns:: Проходит по каждому столбцу с предсказаниями.

rolling_mean = df_to_use[prediction_column].rolling(window).mean().shift(1).values: Вычисляет скользящее среднее для предсказываемого столбца, с окном window, и смещает на 1 период.

rolling_std = df_to_use[prediction_column].rolling(window).std().shift(1).values: Вычисляет скользящее стандартное отклонение для предсказываемого столбца, с окном window, и смещает на 1 период.

upper = rolling_mean + multiplier * rolling_std: Вычисляет верхнюю границу полосы Боллинджера.

lower = rolling_mean - multiplier * rolling_std: Вычисляет нижнюю границу полосы Боллинджера.

pc_shift2 = df_to_use[prediction_column].shift(2).values: Получает предсказанные значения, смещенные на 2 периода назад.

pc_shift1 = df_to_use[prediction_column].shift(1).values: Получает предсказанные значения, смещенные на 1 период назад.

pc = df_to_use[prediction_column].values: Получает текущие предсказанные значения.

condition_buy = (...): Условие для открытия позиции на покупку.

Цена растёт (pc_shift2 < pc_shift1 & pc_shift1 < pc).

Цена достигла или ниже верхней границы полосы Боллинджера.

Нижняя граница полосы Боллинджера больше нижней границы предыдущего бара

condition_sell = (...): Условие для открытия позиции на продажу.

Цена падает (pc_shift2 > pc_shift1 & pc_shift1 > pc).

Цена достигла или выше нижней границы полосы Боллинджера.
* Верхняя граница полосы Боллинджера меньше верхней границы предыдущего бара

signals = np.zeros(len(df_to_use)): Создает массив сигналов, заполненный нулями.

signals[condition_buy] = 1: Устанавливает 1 для сигналов на покупку.

signals[condition_sell] = -1: Устанавливает -1 для сигналов на продажу.

positions = np.zeros(len(df_to_use)): Создает массив позиций, заполненный нулями.

open_position = False: Флаг, указывающий на наличие открытой позиции.

entry_price = 0.0: Переменная для хранения цены открытия позиции.

take_profit = 0.0: Переменная для хранения уровня тейк-профита.

stop_loss = 0.0: Переменная для хранения уровня стоп-лосса.

entry_index = 0: Переменная для хранения индекса открытия позиции.

trade_type = 0: Переменная для хранения типа сделки (1 - покупка, -1 - продажа).

trades_list = []: Список для хранения информации о сделках.

pnl_history = []: Список для хранения истории прибыли и убытков.

time_in_profit = 0: Время, проведенное в прибыльной зоне.

time_in_loss = 0: Время, проведенное в убыточной зоне.

for i in range(len(signals)):
                    if i < 2:
                        positions[i] = 0
                        continue

                    if not open_position:
                        signal = signals[i]
                        if signal != 0:
                            if classifier_sl is not None:
                                if not set(sl_features).issubset(df_to_use.columns):
                                    logging.error("SL features missing.")
                                    positions[i] = 0
                                    continue
                                entry_features = df_to_use.iloc[i][sl_features].values.reshape(1, -1)
                                if 'Close_SL' not in scaler_X_selected_dict:
                                    logging.error("SL scaler not found.")
                                    positions[i] = 0
                                    continue
                                entry_features_scaled = scaler_X_selected_dict['Close_SL'].transform(entry_features)
                                sl_proba = classifier_sl.predict_proba(entry_features_scaled)[0, 1]
                                if sl_proba >= sl_threshold:
                                    positions[i] = 0
                                    continue
                                else:
                                    positions[i] = signal
                            else:
                                positions[i] = signal

                            if signal == 1:
                                entry_price = df_to_use['Ask'].iloc[i]
                            else:
                                entry_price = df_to_use['Bid'].iloc[i]

                            stops_level_points = self.symbol_info.trade_stops_level * self.point if self.symbol_info else self.point * 10
                            if stops_level_points < self.point * 10:
                                stops_level_points = self.point * 10

                            take_profit = entry_price + (tp_factor * rolling_std[i] * signal)
                            stop_loss = entry_price - (sl_factor * rolling_std[i] * signal)

                            if (take_profit - entry_price) * signal < stops_level_points * signal:
                                take_profit = entry_price + (stops_level_points * signal)

                            if (entry_price - stop_loss) * signal < stops_level_points * signal:
                                stop_loss = entry_price - (stops_level_points * signal)

                            open_position = True
                            entry_index = i
                            trade_type = signal
                            time_in_profit = 0
                            time_in_loss = 0
                            pnl_history = []
                        else:
                            positions[i] = 0

Python
for i in range(len(signals)):: Итерируется по всем сигналам.

if i < 2:: Пропускает первые два периода, устанавливая позицию в 0.

if not open_position:: Проверяет, есть ли открытая позиция.

signal = signals[i]: Получает текущий сигнал.

if signal != 0:: Если сигнал не равен 0 (есть сигнал на покупку или продажу):

if classifier_sl is not None:: Проверяет, предоставлен ли классификатор стоп-лосса.

if not set(sl_features).issubset(df_to_use.columns):: Проверяет, есть ли все необходимые фичи для SL в DataFrame. Если нет, выводит сообщение об ошибке, устанавливает позицию в 0 и пропускает итерацию.

entry_features = df_to_use.iloc[i][sl_features].values.reshape(1, -1): Получает фичи для стоп-лосса из DataFrame на текущем индексе.

if 'Close_SL' not in scaler_X_selected_dict:: Проверяет, есть ли скейлер SL в словаре, если нет то выводит сообщение об ошибке, устанавливает позицию в 0 и пропускает итерацию.

entry_features_scaled = scaler_X_selected_dict['Close_SL'].transform(entry_features): Масштабирует фичи для классификатора SL.

sl_proba = classifier_sl.predict_proba(entry_features_scaled)[0, 1]: Получает вероятность срабатывания стоп-лосса.

if sl_proba >= sl_threshold:: Если вероятность срабатывания стоп-лосса больше порога, то устанавливаем позицию в 0 и переходим к следующему бару.

else: positions[i] = signal: В ином случае устанавливаем позицию.

else: positions[i] = signal: Если классификатор SL не предоставлен, то позиция устанавливается согласно сигналу.

if signal == 1:: Если сигнал на покупку:

entry_price = df_to_use['Ask'].iloc[i]: Устанавливает цену входа в позицию по цене Ask.

else: entry_price = df_to_use['Bid'].iloc[i]: Если сигнал на продажу, то цена входа по цене Bid.

stops_level_points = self.symbol_info.trade_stops_level * self.point if self.symbol_info else self.point * 10: Получает минимальное количество пунктов для стоп-лосса из MT5, если не получается то ставит 10 пунктов

if stops_level_points < self.point * 10: stops_level_points = self.point * 10: Проверяет минимальное количество пунктов

take_profit = entry_price + (tp_factor * rolling_std[i] * signal): Вычисляет уровень тейк-профита.

stop_loss = entry_price - (sl_factor * rolling_std[i] * signal): Вычисляет уровень стоп-лосса.

if (take_profit - entry_price) * signal < stops_level_points * signal:: Проверяет, что тейк-профит не меньше чем stops_level_points

take_profit = entry_price + (stops_level_points * signal): Если меньше, то пересчитываем.

if (entry_price - stop_loss) * signal < stops_level_points * signal:: Проверяет, что стоп-лосс не меньше чем stops_level_points

stop_loss = entry_price - (stops_level_points * signal): Если меньше, то пересчитываем.

open_position = True: Устанавливает флаг открытой позиции в True.

entry_index = i: Сохраняет индекс открытия позиции.

trade_type = signal: Сохраняет тип сделки.

time_in_profit = 0: Обнуляем время, проведенное в прибыльной зоне

time_in_loss = 0: Обнуляем время, проведенное в убыточной зоне

pnl_history = []: Обнуляем историю прибыли и убытков.

else: positions[i] = 0: Если сигнала нет, то позиция остаётся 0.

else:: Если открыта позиция.

positions[i] = positions[i - 1]: Сохраняет текущую позицию, как позицию предыдущего бара.

else:
                        positions[i] = positions[i - 1]

                        if trade_type == 1:
                            current_high = df_to_use['High'].iloc[i]
                            current_low = df_to_use['Low'].iloc[i]
                        else:
                            current_high = df_to_use['High'].iloc[i]
                            current_low = df_to_use['Low'].iloc[i]

                        if trade_type == 1:
                            unrealized_pnl = (current_high - entry_price) / self.point
                        else:
                            unrealized_pnl = (entry_price - current_low) / self.point

                        if unrealized_pnl > 0:
                            pnl_history.append(1)
                            time_in_profit += 1
                        elif unrealized_pnl < 0:
                            pnl_history.append(-1)
                            time_in_loss += 1
                        else:
                            pnl_history.append(0)

                        exit_reason = None
                        exit_price = None
                        exit_index = None

                        if trade_type == 1:
                            if current_high >= take_profit:
                                exit_reason = 'TP'
                                exit_price = take_profit
                                exit_index = i
                            elif current_low <= stop_loss:
                                exit_reason = 'SL'
                                exit_price = stop_loss
                                exit_index = i
                            elif signals[i] == -1:
                                exit_reason = 'Opposite Signal'
                                exit_price = current_low
                                exit_index = i
                            elif i == len(signals) - 1:
                                exit_reason = 'Close at End'
                                exit_price = current_high
                                exit_index = i
                        else:
                            if current_low <= take_profit:
                                exit_reason = 'TP'
                                exit_price = take_profit
                                exit_index = i
                            elif current_high >= stop_loss:
                                exit_reason = 'SL'
                                exit_price = stop_loss
                                exit_index = i
                            elif signals[i] == 1:
                                exit_reason = 'Opposite Signal'
                                exit_price = current_high
                                exit_index = i
                            elif i == len(signals) - 1:
                                exit_reason = 'Close at End'
                                exit_price = current_low
                                exit_index = i

Python
if trade_type == 1:: Если тип сделки - покупка:

current_high = df_to_use['High'].iloc[i]: Получаем текущую максимальную цену.

current_low = df_to_use['Low'].iloc[i]: Получаем текущую минимальную цену.

else:: Если тип сделки - продажа:

current_high = df_to_use['High'].iloc[i]: Получаем текущую максимальную цену.

current_low = df_to_use['Low'].iloc[i]: Получаем текущую минимальную цену.

if trade_type == 1:: Если тип сделки - покупка:

unrealized_pnl = (current_high - entry_price) / self.point: Вычисляем нереализованную прибыль, используя текущую максимальную цену и входную цену.

else:: Если тип сделки - продажа:

unrealized_pnl = (entry_price - current_low) / self.point: Вычисляем нереализованную прибыль, используя входную цену и текущую минимальную цену.

if unrealized_pnl > 0:: Если нереализованная прибыль положительна:

pnl_history.append(1): Добавляем 1 в историю прибыли и убытков.

time_in_profit += 1: Увеличиваем время проведенное в прибыльной зоне на 1.

elif unrealized_pnl < 0:: Если нереализованная прибыль отрицательна:

pnl_history.append(-1): Добавляем -1 в историю прибыли и убытков.

time_in_loss += 1: Увеличиваем время проведенное в убыточной зоне на 1.

else:: Если нереализованная прибыль равна 0:

pnl_history.append(0): Добавляем 0 в историю прибыли и убытков.

exit_reason = None: Инициализируем причину закрытия позиции None.

exit_price = None: Инициализируем цену закрытия позиции None.

exit_index = None: Инициализируем индекс закрытия позиции None.

if trade_type == 1:: Если тип сделки - покупка:

if current_high >= take_profit:: Если текущая максимальная цена больше или равна тейк-профиту:

exit_reason = 'TP': Устанавливаем причину закрытия позиции как тейк-профит.

exit_price = take_profit: Устанавливаем цену закрытия позиции как тейк-профит.

exit_index = i: Устанавливаем индекс закрытия позиции.

elif current_low <= stop_loss:: Если текущая минимальная цена меньше или равна стоп-лоссу:

exit_reason = 'SL': Устанавливаем причину закрытия позиции как стоп-лосс.

exit_price = stop_loss: Устанавливаем цену закрытия позиции как стоп-лосс.

exit_index = i: Устанавливаем индекс закрытия позиции.

elif signals[i] == -1:: Если текущий сигнал - продажа:

**`exit_reason = '

59.2s
Продолжайте отвечать, включая последнюю строку ответа.

Хорошо, продолжаем разбор кода с последней строки предыдущего ответа:

exit_reason = 'Opposite Signal'
                                exit_price = current_low
                                exit_index = i
                            elif i == len(signals) - 1:
                                exit_reason = 'Close at End'
                                exit_price = current_high
                                exit_index = i
                        else:
                            if current_low <= take_profit:
                                exit_reason = 'TP'
                                exit_price = take_profit
                                exit_index = i
                            elif current_high >= stop_loss:
                                exit_reason = 'SL'
                                exit_price = stop_loss
                                exit_index = i
                            elif signals[i] == 1:
                                exit_reason = 'Opposite Signal'
                                exit_price = current_high
                                exit_index = i
                            elif i == len(signals) - 1:
                                exit_reason = 'Close at End'
                                exit_price = current_low
                                exit_index = i

Python
exit_reason = 'Opposite Signal': Устанавливаем причину закрытия позиции как "Противоположный сигнал".
* exit_price = current_low: Устанавливаем цену закрытия позиции как текущую минимальную цену.
* exit_index = i: Устанавливаем индекс закрытия позиции.

elif i == len(signals) - 1:: Если это последний бар:

exit_reason = 'Close at End': Устанавливаем причину закрытия позиции как "Закрытие в конце".

exit_price = current_high: Устанавливаем цену закрытия позиции как текущую максимальную цену.

exit_index = i: Устанавливаем индекс закрытия позиции.

else:: Если тип сделки - продажа:

if current_low <= take_profit:: Если текущая минимальная цена меньше или равна тейк-профиту:

exit_reason = 'TP': Устанавливаем причину закрытия позиции как тейк-профит.

exit_price = take_profit: Устанавливаем цену закрытия позиции как тейк-профит.

exit_index = i: Устанавливаем индекс закрытия позиции.

elif current_high >= stop_loss:: Если текущая максимальная цена больше или равна стоп-лоссу:

exit_reason = 'SL': Устанавливаем причину закрытия позиции как стоп-лосс.

exit_price = stop_loss: Устанавливаем цену закрытия позиции как стоп-лосс.

exit_index = i: Устанавливаем индекс закрытия позиции.

elif signals[i] == 1:: Если текущий сигнал - покупка:

exit_reason = 'Opposite Signal': Устанавливаем причину закрытия позиции как "Противоположный сигнал".

exit_price = current_high: Устанавливаем цену закрытия позиции как текущую максимальную цену.

exit_index = i: Устанавливаем индекс закрытия позиции.

elif i == len(signals) - 1:: Если это последний бар:

exit_reason = 'Close at End': Устанавливаем причину закрытия позиции как "Закрытие в конце".

exit_price = current_low: Устанавливаем цену закрытия позиции как текущую минимальную цену.

exit_index = i: Устанавливаем индекс закрытия позиции.

if exit_reason:
                            positions[i] = 0
                            open_position = False
                            trade_duration = exit_index - entry_index
                            trade_duration = trade_duration if trade_duration > 0 else 1
                            half_duration = (trade_duration + 1) // 2

                            first_half = pnl_history[:half_duration]
                            second_half = pnl_history[half_duration:]

                            time_in_profit_first_half = first_half.count(1)
                            time_in_loss_first_half = first_half.count(-1)
                            time_in_profit_second_half = second_half.count(1)
                            time_in_loss_second_half = second_half.count(-1)

                            if trade_type == 1:
                                points = (exit_price - entry_price) / self.point
                            else:
                                points = (entry_price - exit_price) / self.point

                            points_eur = points * self.currency_conversion_rate

                            total_time_first_half = len(first_half)
                            total_time_second_half = len(second_half)
                            profit_time_ratio_first_half = (
                                time_in_profit_first_half / total_time_first_half) if total_time_first_half > 0 else 0
                            loss_time_ratio_first_half = (
                                time_in_loss_first_half / total_time_first_half) if total_time_first_half > 0 else 0
                            profit_time_ratio_second_half = (
                                time_in_profit_second_half / total_time_second_half) if total_time_second_half > 0 else 0
                            loss_time_ratio_second_half = (
                                time_in_loss_second_half / total_time_second_half) if total_time_second_half > 0 else 0

                            trades_list.append({
                                'Entry_Index': entry_index,
                                'Exit_Index': exit_index,
                                'Entry_Date': df_to_use.index[entry_index],
                                'Exit_Date': df_to_use.index[exit_index],
                                'Entry_Price': entry_price,
                                'Exit_Price': exit_price,
                                'Trade_Type': trade_type,
                                'Exit_Reason': exit_reason,
                                'Hit_SL': 1 if exit_reason == 'SL' else 0,
                                'Points': points_eur,
                                'Duration': trade_duration,
                                'Time_in_Profit_First_Half': time_in_profit_first_half,
                                'Time_in_Loss_First_Half': time_in_loss_first_half,
                                'Time_in_Profit_Second_Half': time_in_profit_second_half,
                                'Time_in_Loss_Second_Half': time_in_loss_second_half,
                                'Profit_Time_Ratio_First_Half': profit_time_ratio_first_half,
                                'Loss_Time_Ratio_First_Half': loss_time_ratio_first_half,
                                'Profit_Time_Ratio_Second_Half': profit_time_ratio_second_half,
                                'Loss_Time_Ratio_Second_Half': loss_time_ratio_second_half,
                                'Profit_Time_Ratio': (time_in_profit / trade_duration) if trade_duration > 0 else 0,
                                'Loss_Time_Ratio': (time_in_loss / trade_duration) if trade_duration > 0 else 0,
                                'Time_in_Profit': time_in_profit,
                                'Time_in_Loss': time_in_loss
                            })

Python
if exit_reason:: Если определена причина закрытия позиции:

positions[i] = 0: Устанавливаем текущую позицию в 0.

open_position = False: Устанавливаем флаг открытой позиции в False.

trade_duration = exit_index - entry_index: Вычисляем длительность сделки.

trade_duration = trade_duration if trade_duration > 0 else 1: Если длительность сделки равна или меньше 0, то устанавливаем её как 1.

half_duration = (trade_duration + 1) // 2: Вычисляем половину длительности сделки.

first_half = pnl_history[:half_duration]: Получаем историю прибыли и убытков за первую половину сделки.

second_half = pnl_history[half_duration:]: Получаем историю прибыли и убытков за вторую половину сделки.

time_in_profit_first_half = first_half.count(1): Считаем время, проведенное в прибыли в первой половине

time_in_loss_first_half = first_half.count(-1): Считаем время, проведенное в убытке в первой половине

time_in_profit_second_half = second_half.count(1): Считаем время, проведенное в прибыли во второй половине

time_in_loss_second_half = second_half.count(-1): Считаем время, проведенное в убытке во второй половине

if trade_type == 1:: Если тип сделки - покупка:

points = (exit_price - entry_price) / self.point: Вычисляем количество пунктов, на которые изменилась цена.

else:: Если тип сделки - продажа:

points = (entry_price - exit_price) / self.point: Вычисляем количество пунктов, на которые изменилась цена.

points_eur = points * self.currency_conversion_rate: Пересчитываем пункты в валюту депозита.

total_time_first_half = len(first_half): Получаем общее время первой половины

total_time_second_half = len(second_half): Получаем общее время второй половины

profit_time_ratio_first_half = ...: Вычисляем долю времени, проведенную в прибыли в первой половине.

loss_time_ratio_first_half = ...: Вычисляем долю времени, проведенную в убытке в первой половине.

profit_time_ratio_second_half = ...: Вычисляем долю времени, проведенную в прибыли во второй половине.

loss_time_ratio_second_half = ...: Вычисляем долю времени, проведенную в убытке во второй половине.

trades_list.append({ ... }): Добавляем информацию о сделке в список trades_list, включая:

Entry_Index: Индекс открытия позиции.

Exit_Index: Индекс закрытия позиции.

Entry_Date: Дата открытия позиции.

Exit_Date: Дата закрытия позиции.

Entry_Price: Цена открытия позиции.

Exit_Price: Цена закрытия позиции.

Trade_Type: Тип сделки (1 или -1).

Exit_Reason: Причина закрытия позиции.

Hit_SL: Флаг, был ли достигнут стоп-лосс (1 или 0).

Points: Прибыль или убыток в пунктах, выраженные в валюте депозита.

Duration: Продолжительность сделки.

Time_in_Profit_First_Half: Время, проведенное в прибыли в первой половине сделки.

Time_in_Loss_First_Half: Время, проведенное в убытке в первой половине сделки.

Time_in_Profit_Second_Half: Время, проведенное в прибыли во второй половине сделки.

Time_in_Loss_Second_Half: Время, проведенное в убытке во второй половине сделки.

Profit_Time_Ratio_First_Half: Доля времени, проведенного в прибыли в первой половине сделки.

Loss_Time_Ratio_First_Half: Доля времени, проведенного в убытке в первой половине сделки.

Profit_Time_Ratio_Second_Half: Доля времени, проведенного в прибыли во второй половине сделки.

Loss_Time_Ratio_Second_Half: Доля времени, проведенного в убытке во второй половине сделки.

Profit_Time_Ratio: Доля времени, проведенного в прибыли для всей сделки.

Loss_Time_Ratio: Доля времени, проведенного в убытке для всей сделки.

Time_in_Profit: Время, проведенное в прибыльной зоне

Time_in_Loss: Время, проведенное в убыточной зоне

market_returns = df_to_use['Close'].pct_change().fillna(0).values
                strategy_returns = np.roll(positions, 1) * market_returns
                trades = np.abs(np.diff(positions, prepend=0))
                net_returns = strategy_returns * self.currency_conversion_rate - trades * transaction_cost - trades * slippage

                # Change made here: Include data_source in column name
                df_to_use[f'Strategy_Return_Net_{prediction_column}_({data_source})'] = net_returns
                self.trades_dict[prediction_column] = trades_list

                net_returns_series = pd.Series(net_returns, index=df_to_use.index)
                target_return = 0
                downside_returns = net_returns_series[net_returns_series < target_return]
                downside_std = downside_returns.std()

                sortino_ratio = (
                    (net_returns_series.mean() - risk_free_rate) / downside_std if downside_std != 0 else 0
                )

                if sortino_ratio <= 0:
                    sortino_ratio = 1e-10

                sortino_ratios.append(sortino_ratio)

Python
market_returns = df_to_use['Close'].pct_change().fillna(0).values: Вычисляем процентное изменение цены закрытия и заполняем отсутствующие значения (NaN) нулями.

strategy_returns = np.roll(positions, 1) * market_returns: Вычисляем доходность стратегии, умножая смещенные на 1 позицию значения на доходность рынка.

trades = np.abs(np.diff(positions, prepend=0)): Вычисляем количество сделок, находя разницу между текущими и предыдущими позициями.

net_returns = strategy_returns * self.currency_conversion_rate - trades * transaction_cost - trades * slippage: Вычисляем чистую доходность, вычитая издержки по транзакциям и проскальзыванию.

df_to_use[f'Strategy_Return_Net_{prediction_column}_({data_source})'] = net_returns: Добавляет столбец с чистой доходностью стратегии в DataFrame с названием, включающим имя предсказываемого столбца и источник данных.

self.trades_dict[prediction_column] = trades_list: Сохраняем информацию о сделках для данного предсказываемого столбца.

net_returns_series = pd.Series(net_returns, index=df_to_use.index): Создаем временной ряд из чистой доходности стратегии с индексом из df_to_use.

target_return = 0: Устанавливаем целевую доходность на 0.

downside_returns = net_returns_series[net_returns_series < target_return]: Получаем ряд с доходностями ниже целевой (отрицательными).

downside_std = downside_returns.std(): Вычисляем стандартное отклонение отрицательных доходностей.

sortino_ratio = (net_returns_series.mean() - risk_free_rate) / downside_std if downside_std != 0 else 0: Вычисляем коэффициент Сортино.

if sortino_ratio <= 0: sortino_ratio = 1e-10: Если коэффициент Сортино меньше или равен 0, то устанавливаем минимальное значение (1e-10), чтобы избежать деления на 0 в дальнейших расчетах.

sortino_ratios.append(sortino_ratio): Добавляем коэффициент Сортино в список.

if len(sortino_ratios) != len(self.prediction_columns):
                logging.error("Sortino ratios length mismatch. Assigning minimal fitness.")
                sortino_ratios = [1e-10] * len(self.prediction_columns)

            return tuple(sortino_ratios)

        except KeyError as ke:
            logging.error(f"KeyError in evaluate function: {ke}")
            return tuple([1e-10] * len(self.prediction_columns))
        except Exception as e:
            logging.error(f"Error in evaluate function: {e}")
            return tuple([1e-10] * len(self.prediction_columns))

Python
if len(sortino_ratios) != len(self.prediction_columns):: Проверяем, что длина списка коэффициентов Сортино совпадает с количеством предсказываемых столбцов.

logging.error("Sortino ratios length mismatch. Assigning minimal fitness."): Если не совпадает, выводим ошибку и возвращаем минимальный фитнес.

sortino_ratios = [1e-10] * len(self.prediction_columns): Устанавливаем минимальный фитнес.

return tuple(sortino_ratios): Возвращает коэффициенты Сортино как кортеж.

except KeyError as ke:: Перехватывает ошибку KeyError, возникающую, если нет ключа в словаре.

logging.error(f"KeyError in evaluate function: {ke}"): Выводим сообщение об ошибке.

return tuple([1e-10] * len(self.prediction_columns)): Возвращаем минимальный фитнес.

except Exception as e:: Перехватывает все остальные исключения.

logging.error(f"Error in evaluate function: {e}"): Выводим сообщение об ошибке.

return tuple([1e-10] * len(self.prediction_columns)): Возвращаем минимальный фитнес.

# --------------------- Plotting Functions ---------------------
if show_plots:
    def plot_error_distribution(test, symbol, timeframe_name, prediction_method, plot_dir, data_source):
        try:
            # Adjusted column name to include data_source
            error_col = f'Strategy_Return_Net_{prediction_method}_({data_source})'
            if error_col not in test.columns:
                logging.error(f"'{error_col}' column is missing in the test DataFrame.")
                return
            errors = test[error_col]
            errors_in_pips = errors * pip_scale_factor
            plt.figure(figsize=(10, 6))
            plt.hist(errors_in_pips, bins=50, alpha=0.7, color='blue', edgecolor='black')
            plt.title(
                f'Distribution of Prediction Errors for {symbol} ({timeframe_name})\nPrediction Method:  {prediction_method}_({data_source})')
            plt.xlabel('Error (Actual - Predicted) in Pips')
            plt.ylabel('Frequency')
            plt.grid(True)
            plt.savefig(os.path.join(plot_dir, f'error_distribution_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()
        except Exception as e:
            logging.error(f"Error plotting error distribution: {e}")

Python
if show_plots:: Проверяем, включена ли отрисовка графиков.

def plot_error_distribution(test, symbol, timeframe_name, prediction_method, plot_dir, data_source):: Определяем функцию для отрисовки распределения ошибок.

test: DataFrame с тестовыми данными.

symbol: Символ валютной пары.

timeframe_name: Имя таймфрейма.

prediction_method: Метод предсказания.

plot_dir: Директория для сохранения графиков

data_source: Источник данных

try ... except Exception as e:: Обработка возможных исключений.

error_col = f'Strategy_Return_Net_{prediction_method}_({data_source})': Формируем название столбца с ошибками, включая источник данных.

if error_col not in test.columns:: Если нет нужного столбца в DataFrame, выводим ошибку и выходим из функции.

errors = test[error_col]: Получаем ошибки.

errors_in_pips = errors * pip_scale_factor: Умножаем ошибки на коэффициент для отображения в пипсах.

plt.figure(figsize=(10, 6)): Создаем новое изображение.

plt.hist(errors_in_pips, bins=50, alpha=0.7, color='blue', edgecolor='black'): Строим гистограмму распределения ошибок.

plt.title(...): Устанавливаем заголовок графика.

plt.xlabel('Error (Actual - Predicted) in Pips'): Устанавливаем подпись оси X.

plt.ylabel('Frequency'): Устанавливаем подпись оси Y.

plt.grid(True): Включаем сетку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

except Exception as e:: Перехватываем все ошибки.

logging.error(f"Error plotting error distribution: {e}"): Выводим сообщение об ошибке в лог.

def plot_residuals(test, symbol, timeframe_name, prediction_method, plot_dir, data_source):
        try:
            # Adjusted column name to include data_source
            error_col = f'Strategy_Return_Net_{prediction_method}_({data_source})'
            if error_col not in test.columns:
                logging.error(f"'{error_col}' column is missing in the test DataFrame.")
                return
            residuals = test[error_col]
            residuals_in_pips = residuals * pip_scale_factor
            plt.figure(figsize=(14, 7))
            plt.scatter(test.index, residuals_in_pips, alpha=0.5)
            plt.title(f'Residuals Over Time for {symbol} ({timeframe_name}) -  {prediction_method}_({data_source})')
            plt.xlabel('Date')
            plt.ylabel('Residual (Actual - Predicted) in Pips')
            plt.grid(True)
            plt.savefig(os.path.join(plot_dir, f'residuals_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()
        except Exception as e:
            logging.error(f"Error plotting residuals: {e}")

Python
def plot_residuals(test, symbol, timeframe_name, prediction_method, plot_dir, data_source):: Определяем функцию для отрисовки остатков.

Аналогично plot_error_distribution (параметры и структура кода).

plt.scatter(test.index, residuals_in_pips, alpha=0.5): Строим точечную диаграмму остатков.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Date'): Устанавливаем подпись оси X.

plt.ylabel('Residual (Actual - Predicted) in Pips'): Устанавливаем подпись оси Y.

plt.grid(True): Включаем сетку.

plt.savefig(...): Сохраняем график.

plt.close(): Закрываем изображение.

def backtest_strategy(test, symbol, timeframe_name, prediction_method, label_suffix, plot_dir, data_source):
        try:
            net_return_column = f'Strategy_Return_Net_{prediction_method}_({data_source})'
            if net_return_column not in test.columns:
                logging.error(f"'{net_return_column}' column is missing in the test DataFrame.")
                return

            cumulative_returns = (1 + test[net_return_column].fillna(0)).cumprod() - 1
            plt.figure(figsize=(14, 7))
            plt.plot(test.index, cumulative_returns, label=f'Cumulative Return ({label_suffix})', color='purple')
            plt.title(f'Cumulative Return for {symbol} ({timeframe_name}) -  {prediction_method}_({data_source})')
            plt.xlabel('Date')
            plt.ylabel('Cumulative Return')
            plt.legend()
            plt.grid(True)
            plt.savefig(os.path.join(plot_dir, f'cumulative_returns_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()
        except Exception as e:
            logging.error(f"Error in backtesting: {e}")

Python
def backtest_strategy(test, symbol, timeframe_name, prediction_method, label_suffix, plot_dir, data_source):: Определяем функцию для отрисовки графика кумулятивной доходности стратегии.

test: DataFrame с тестовыми данными.

symbol: Символ валютной пары.

timeframe_name: Имя таймфрейма.

prediction_method: Метод предсказания.

label_suffix: Суффикс для лейбла.

plot_dir: Директория для сохранения графиков

data_source: Источник данных

try ... except Exception as e:: Обработка возможных исключений.

net_return_column = f'Strategy_Return_Net_{prediction_method}_({data_source})': Создаем название столбца с чистой доходностью, включая источник данных.

if net_return_column not in test.columns:: Проверяем наличие столбца в DataFrame.

cumulative_returns = (1 + test[net_return_column].fillna(0)).cumprod() - 1: Вычисляем кумулятивную доходность.

plt.figure(figsize=(14, 7)): Создаем новое изображение.

plt.plot(test.index, cumulative_returns, label=f'Cumulative Return ({label_suffix})', color='purple'): Строим график кумулятивной доходности.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Date'): Устанавливаем подпись оси X.

plt.ylabel('Cumulative Return'): Устанавливаем подпись оси Y.

plt.legend(): Включаем легенду.

plt.grid(True): Включаем сетку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

except Exception as e:: Перехватываем все ошибки.

logging.error(f"Error in backtesting: {e}"): Выводим сообщение об ошибке в лог.

EXIT_REASON_PALETTE = {
        'TP': 'green',
        'SL': 'red',
        'Opposite Signal (Profit)': 'lime',
        'Opposite Signal (Loss)': 'darkred',
        'Close at End': 'blue',
    }

Python
EXIT_REASON_PALETTE = { ... }: Определяем словарь с цветами для разных причин закрытия позиции.

PROFIT_LOSS_PALETTE = {
        'Profit': 'green',
        'Loss': 'red'
    }

Python
PROFIT_LOSS_PALETTE = { ... }: Определяем словарь с цветами для прибыльных и убыточных сделок.

def plot_trade_metrics(trades_list, symbol, timeframe_name, prediction_method, plot_dir, data_source):
        try:
            df_trades = pd.DataFrame(trades_list)
            if df_trades.empty:
                logging.warning(f"No trades to plot for {symbol} ({timeframe_name}) -  {prediction_method}_({data_source}).")
                return

            df_trades['Exit_Reason_Detailed'] = df_trades.apply(
                lambda row: 'Opposite Signal (Profit)' if row['Exit_Reason'] == 'Opposite Signal' and row['Points'] > 0
                else ('Opposite Signal (Loss)' if row['Exit_Reason'] == 'Opposite Signal' and row['Points'] <= 0
                      else row['Exit_Reason']),
                axis=1
            )

            plt.figure(figsize=(12, 6))
            sns.barplot(x='Exit_Reason_Detailed', y='Points', data=df_trades, palette=EXIT_REASON_PALETTE)
            plt.title(f'Position Lengths in Pips for {symbol} ({timeframe_name}) -  {prediction_method}_({data_source})')
            plt.xlabel('Exit Reason')
            plt.ylabel('Points')
            plt.tight_layout()
            plt.savefig(os.path.join(plot_dir, f'position_lengths_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()

Python
def plot_trade_metrics(trades_list, symbol, timeframe_name, prediction_method, plot_dir, data_source):: Определяем функцию для отрисовки различных метрик торговли.

trades_list: Список сделок.

symbol: Символ валютной пары.

timeframe_name: Имя таймфрейма.

prediction_method: Метод предсказания.

plot_dir: Директория для сохранения графиков

data_source: Источник данных

try ... except Exception as e:: Обработка возможных исключений.

df_trades = pd.DataFrame(trades_list): Преобразуем список сделок в DataFrame.

if df_trades.empty:: Проверяем, есть ли сделки для отрисовки. Если нет, выводим предупреждение и выходим из функции.

df_trades['Exit_Reason_Detailed'] = df_trades.apply(...): Создаем новый столбец Exit_Reason_Detailed, уточняя причины закрытия позиции (прибыль или убыток для "Противоположного сигнала").

plt.figure(figsize=(12, 6)): Создаем новое изображение.

sns.barplot(x='Exit_Reason_Detailed', y='Points', data=df_trades, palette=EXIT_REASON_PALETTE): Строим столбчатую диаграмму длины позиций в пипсах по причинам закрытия.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Exit Reason'): Устанавливаем подпись оси X.

plt.ylabel('Points'): Устанавливаем подпись оси Y.

plt.tight_layout(): Уплотняем разметку графика.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

# Number of Winning and Losing Trades
            num_winning_trades = len(df_trades[df_trades['Points'] > 0])
            num_losing_trades = len(df_trades[df_trades['Points'] <= 0])
            total_pips_winning = df_trades[df_trades['Points'] > 0]['Points'].sum()
            total_pips_losing = df_trades[df_trades['Points'] <= 0]['Points'].sum()

            fig, ax1 = plt.subplots(figsize=(10, 6))
            bar_width = 0.35
            indices = np.arange(2)

            bars1 = ax1.bar(indices - bar_width / 2, [num_winning_trades, num_losing_trades],
                            bar_width, label='Number of Trades',
                            color=['green', 'red'], alpha=0.7, edgecolor='black')

            ax1.set_xlabel('Trade Outcome', fontsize=14)
            ax1.set_ylabel('Number of Trades', color='black', fontsize=14)
            ax1.set_title(
                f'Number of and Total Pips in Winning and Losing Trades\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})',
                fontsize=16)
            ax1.set_xticks(indices)
            ax1.set_xticklabels(['Winning Trades', 'Losing Trades'])
            ax1.tick_params(axis='

Python
83.2s
Продолжайте отвечать, включая последнюю строку ответа.

Хорошо, продолжаем разбор кода с последней строки предыдущего ответа:

ax1.tick_params(axis='y', labelcolor='black')

            ax2 = ax1.twinx()
            bars2 = ax2.bar(indices + bar_width / 2, [total_pips_winning, total_pips_losing],
                            bar_width, label='Total Pips',
                            color=['darkgreen', 'darkred'], alpha=0.7, edgecolor='black')

            ax2.set_ylabel('Total Pips', color='black', fontsize=14)
            ax2.tick_params(axis='y', labelcolor='black')

            lines1, labels1 = ax1.get_legend_handles_labels()
            lines2, labels2 = ax2.get_legend_handles_labels()
            ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper right')

            for bar in bars1:
                height = bar.get_height()
                ax1.annotate(f'{height}',
                             xy=(bar.get_x() + bar.get_width() / 2, height),
                             xytext=(0, 3),
                             textcoords="offset points",
                             ha='center', va='bottom',
                             fontsize=10, fontweight='bold')

            for bar in bars2:
                height = bar.get_height()
                ax2.annotate(f'{height:.1f}',
                             xy=(bar.get_x() + bar.get_width() / 2, height),
                             xytext=(0, 3),
                             textcoords="offset points",
                             ha='center', va='bottom',
                             fontsize=10, fontweight='bold')

            plt.grid(True, axis='y', linestyle='--', alpha=0.7)
            plt.tight_layout()
            plt.savefig(os.path.join(plot_dir,
                                     f'winning_losing_trades_and_pips_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()

Python
num_winning_trades = len(df_trades[df_trades['Points'] > 0]): Вычисляем количество прибыльных сделок.

num_losing_trades = len(df_trades[df_trades['Points'] <= 0]): Вычисляем количество убыточных сделок.

total_pips_winning = df_trades[df_trades['Points'] > 0]['Points'].sum(): Вычисляем суммарную прибыль в пипсах по всем прибыльным сделкам.

total_pips_losing = df_trades[df_trades['Points'] <= 0]['Points'].sum(): Вычисляем суммарный убыток в пипсах по всем убыточным сделкам.

fig, ax1 = plt.subplots(figsize=(10, 6)): Создаем новое изображение и оси для первого графика.

bar_width = 0.35: Задаем ширину столбцов.

indices = np.arange(2): Создаем массив индексов для двух столбцов.

bars1 = ax1.bar(indices - bar_width / 2, [num_winning_trades, num_losing_trades], ...): Строим столбчатую диаграмму для количества прибыльных и убыточных сделок.

ax1.set_xlabel('Trade Outcome', fontsize=14): Устанавливаем подпись оси X.

ax1.set_ylabel('Number of Trades', color='black', fontsize=14): Устанавливаем подпись оси Y.

ax1.set_title(...): Устанавливаем заголовок.

ax1.set_xticks(indices): Устанавливаем отметки оси X.

ax1.set_xticklabels(['Winning Trades', 'Losing Trades']): Устанавливаем подписи отметок оси X.

ax1.tick_params(axis='y', labelcolor='black'): Задаём цвет подписей оси Y.

ax2 = ax1.twinx(): Создаем вторые оси, которые будут накладываться на первые.

bars2 = ax2.bar(indices + bar_width / 2, [total_pips_winning, total_pips_losing], ...): Строим столбчатую диаграмму для суммарной прибыли и убытка в пипсах.

ax2.set_ylabel('Total Pips', color='black', fontsize=14): Устанавливаем подпись оси Y для вторых осей.

ax2.tick_params(axis='y', labelcolor='black'): Задаём цвет подписей оси Y для вторых осей.

lines1, labels1 = ax1.get_legend_handles_labels(): Получаем дескрипторы и подписи для первого графика.

lines2, labels2 = ax2.get_legend_handles_labels(): Получаем дескрипторы и подписи для второго графика.

ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper right'): Создаём общую легенду для обоих графиков.

for bar in bars1:: Итерируемся по столбцам для отрисовки значений на них.

height = bar.get_height(): Получаем высоту текущего столбца.

ax1.annotate(f'{height}', ...): Отрисовываем значение высоты столбца над ним.

for bar in bars2:: Итерируемся по столбцам для отрисовки значений на них.

height = bar.get_height(): Получаем высоту текущего столбца.

ax2.annotate(f'{height:.1f}', ...): Отрисовываем значение высоты столбца над ним.

plt.grid(True, axis='y', linestyle='--', alpha=0.7): Включаем сетку только по оси Y.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

# Profit and Loss Distribution
            df_trades['Profit_Loss'] = df_trades['Points'].apply(lambda x: 'Profit' if x > 0 else 'Loss')

            plt.figure(figsize=(12, 6))
            sns.histplot(
                data=df_trades,
                x='Points',
                hue='Profit_Loss',
                multiple='stack',
                bins=50,
                palette=PROFIT_LOSS_PALETTE,
                alpha=0.7
            )

            plt.title(f'Profit and Loss Distribution (in Points)\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})',
                      fontsize=16)
            plt.xlabel('Profit/Loss (Points)', fontsize=14)
            plt.ylabel('Number of Trades', fontsize=14)
            plt.legend(title='Trade Outcome')
            plt.grid(True, linestyle='--', alpha=0.7)
            plt.tight_layout()
            plt.savefig(
                os.path.join(plot_dir, f'profit_loss_distribution_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()

Python
df_trades['Profit_Loss'] = df_trades['Points'].apply(lambda x: 'Profit' if x > 0 else 'Loss'): Создаем новый столбец 'Profit_Loss', указывающий на то является ли сделка прибыльной или убыточной.

plt.figure(figsize=(12, 6)): Создаем новое изображение.

sns.histplot(data=df_trades, x='Points', hue='Profit_Loss', multiple='stack', bins=50, palette=PROFIT_LOSS_PALETTE, alpha=0.7): Строим гистограмму распределения прибыли и убытков.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Profit/Loss (Points)', fontsize=14): Устанавливаем подпись оси X.

plt.ylabel('Number of Trades', fontsize=14): Устанавливаем подпись оси Y.

plt.legend(title='Trade Outcome'): Включаем легенду.

plt.grid(True, linestyle='--', alpha=0.7): Включаем сетку.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

# Cumulative Profit Over Time
            df_trades_sorted = df_trades.sort_values('Exit_Date')
            df_trades_sorted['Cumulative_Points'] = df_trades_sorted['Points'].cumsum()

            plt.figure(figsize=(14, 7))
            plt.plot(df_trades_sorted['Exit_Date'], df_trades_sorted['Cumulative_Points'],
                     label='Cumulative Profit (Points)', color='purple', linewidth=2)
            plt.title(f'Cumulative Profit Over Time\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})', fontsize=16)
            plt.xlabel('Date', fontsize=14)
            plt.ylabel('Cumulative Profit (Points)', fontsize=14)
            plt.legend()
            plt.grid(True)
            plt.savefig(os.path.join(plot_dir, f'cumulative_profit_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()

Python
df_trades_sorted = df_trades.sort_values('Exit_Date'): Сортируем DataFrame со сделками по дате закрытия.

df_trades_sorted['Cumulative_Points'] = df_trades_sorted['Points'].cumsum(): Вычисляем кумулятивную прибыль.

plt.figure(figsize=(14, 7)): Создаем новое изображение.

plt.plot(df_trades_sorted['Exit_Date'], df_trades_sorted['Cumulative_Points'], ...): Строим график кумулятивной прибыли.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Date', fontsize=14): Устанавливаем подпись оси X.

plt.ylabel('Cumulative Profit (Points)', fontsize=14): Устанавливаем подпись оси Y.

plt.legend(): Включаем легенду.

plt.grid(True): Включаем сетку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

# Average Time in Profit and Loss for Profitable Trades
            profitable_trades = df_trades[df_trades['Points'] > 0]
            if not profitable_trades.empty:
                avg_time_in_profit = profitable_trades['Time_in_Profit'].mean()
                avg_time_in_loss = profitable_trades['Time_in_Loss'].mean()

                labels = ['Time in Profit', 'Time in Loss']
                times = [avg_time_in_profit, avg_time_in_loss]

                plt.figure(figsize=(8, 6))
                plt.bar(labels, times, color=['green', 'red'], alpha=0.7, edgecolor='black')
                plt.title(
                    f'Average Time in Profit and Loss for Profitable Trades\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})',
                    fontsize=16)
                plt.ylabel('Time (Number of Periods)', fontsize=14)
                plt.grid(True, axis='y', linestyle='--', alpha=0.7)
                plt.tight_layout()
                plt.savefig(
                    os.path.join(plot_dir, f'avg_time_profit_loss_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
                plt.close()

Python
profitable_trades = df_trades[df_trades['Points'] > 0]: Получаем DataFrame только с прибыльными сделками.

if not profitable_trades.empty:: Проверяем, есть ли прибыльные сделки для отрисовки.

avg_time_in_profit = profitable_trades['Time_in_Profit'].mean(): Вычисляем среднее время, проведенное в прибыли для прибыльных сделок.

avg_time_in_loss = profitable_trades['Time_in_Loss'].mean(): Вычисляем среднее время, проведенное в убытке для прибыльных сделок.

labels = ['Time in Profit', 'Time in Loss']: Задаем подписи для графика.

times = [avg_time_in_profit, avg_time_in_loss]: Задаем значения для графика.

plt.figure(figsize=(8, 6)): Создаем новое изображение.

plt.bar(labels, times, color=['green', 'red'], alpha=0.7, edgecolor='black'): Строим столбчатую диаграмму для среднего времени в прибыли и убытке.

plt.title(...): Устанавливаем заголовок.

plt.ylabel('Time (Number of Periods)', fontsize=14): Устанавливаем подпись оси Y.

plt.grid(True, axis='y', linestyle='--', alpha=0.7): Включаем сетку.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

# Average Time in Profit and Loss for Losing Trades
            losing_trades = df_trades[df_trades['Points'] <= 0]
            if not losing_trades.empty:
                avg_time_in_profit_loss = losing_trades['Time_in_Profit'].mean()
                avg_time_in_loss_loss = losing_trades['Time_in_Loss'].mean()

                labels = ['Time in Profit', 'Time in Loss']
                times = [avg_time_in_profit_loss, avg_time_in_loss_loss]

                plt.figure(figsize=(8, 6))
                plt.bar(labels, times, color=['green', 'red'], alpha=0.7, edgecolor='black')
                plt.title(
                    f'Average Time in Profit and Loss for Losing Trades\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})',
                    fontsize=16)
                plt.ylabel('Time (Number of Periods)', fontsize=14)
                plt.grid(True, axis='y', linestyle='--', alpha=0.7)
                plt.tight_layout()
                plt.savefig(os.path.join(plot_dir,
                                         f'avg_time_profit_loss_losing_trades_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
                plt.close()

Python
losing_trades = df_trades[df_trades['Points'] <= 0]: Получаем DataFrame только с убыточными сделками.

if not losing_trades.empty:: Проверяем, есть ли убыточные сделки для отрисовки.

avg_time_in_profit_loss = losing_trades['Time_in_Profit'].mean(): Вычисляем среднее время, проведенное в прибыли для убыточных сделок.

avg_time_in_loss_loss = losing_trades['Time_in_Loss'].mean(): Вычисляем среднее время, проведенное в убытке для убыточных сделок.

labels = ['Time in Profit', 'Time in Loss']: Задаем подписи для графика.

times = [avg_time_in_profit_loss, avg_time_in_loss_loss]: Задаем значения для графика.

plt.figure(figsize=(8, 6)): Создаем новое изображение.

plt.bar(labels, times, color=['green', 'red'], alpha=0.7, edgecolor='black'): Строим столбчатую диаграмму для среднего времени в прибыли и убытке.

plt.title(...): Устанавливаем заголовок.

plt.ylabel('Time (Number of Periods)', fontsize=14): Устанавливаем подпись оси Y.

plt.grid(True, axis='y', linestyle='--', alpha=0.7): Включаем сетку.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

# Trade Duration Distribution
            plt.figure(figsize=(12, 6))
            sns.histplot(df_trades['Duration'], bins=50, kde=True, color='orange', alpha=0.7)
            plt.title(f'Trade Duration Distribution\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})', fontsize=16)
            plt.xlabel('Duration (Number of Periods)', fontsize=14)
            plt.ylabel('Number of Trades', fontsize=14)
            plt.grid(True, linestyle='--', alpha=0.7)
            plt.tight_layout()
            plt.savefig(os.path.join(plot_dir, f'trade_duration_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()

Python
plt.figure(figsize=(12, 6)): Создаем новое изображение.

sns.histplot(df_trades['Duration'], bins=50, kde=True, color='orange', alpha=0.7): Строим гистограмму распределения длительности сделок.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Duration (Number of Periods)', fontsize=14): Устанавливаем подпись оси X.

plt.ylabel('Number of Trades', fontsize=14): Устанавливаем подпись оси Y.

plt.grid(True, linestyle='--', alpha=0.7): Включаем сетку.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

# Boxplot for Time in Profit and Loss - First and Second Half
            plt.figure(figsize=(14, 7))
            sns.boxplot(data=df_trades[['Time_in_Profit_First_Half', 'Time_in_Loss_First_Half',
                                        'Time_in_Profit_Second_Half', 'Time_in_Loss_Second_Half']],
                        palette='Set3')
            plt.title(
                f'Time in Profit and Loss - First and Second Half\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})',
                fontsize=16)
            plt.xlabel('Trade Phase')
            plt.ylabel('Time (Number of Periods)')
            plt.tight_layout()
            plt.savefig(
                os.path.join(plot_dir, f'time_profit_loss_half_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()

Python
plt.figure(figsize=(14, 7)): Создаем новое изображение.

sns.boxplot(data=df_trades[[...]], palette='Set3'): Строим ящики с усами для времени в прибыли и убытке в первой и второй половинах сделки.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Trade Phase'): Устанавливаем подпись оси X.

plt.ylabel('Time (Number of Periods)'): Устанавливаем подпись оси Y.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

# Average Profit and Loss Ratios by Phase
            avg_profit_ratio_first_half = df_trades['Profit_Time_Ratio_First_Half'].mean()
            avg_loss_ratio_first_half = df_trades['Loss_Time_Ratio_First_Half'].mean()
            avg_profit_ratio_second_half = df_trades['Profit_Time_Ratio_Second_Half'].mean()
            avg_loss_ratio_second_half = df_trades['Loss_Time_Ratio_Second_Half'].mean()

            ratios = pd.DataFrame({
                'Phase': ['First Half', 'Second Half'],
                'Profit Ratio': [avg_profit_ratio_first_half, avg_profit_ratio_second_half],
                'Loss Ratio': [avg_loss_ratio_first_half, avg_loss_ratio_second_half]
            })

            ratios_melted = ratios.melt(id_vars='Phase', value_vars=['Profit Ratio', 'Loss Ratio'],
                                        var_name='Type', value_name='Ratio')

            plt.figure(figsize=(10, 6))
            sns.barplot(x='Phase', y='Ratio', hue='Type', data=ratios_melted, palette=['green', 'red'])
            plt.title(f'Average Profit and Loss Ratios by Phase\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})',
                      fontsize=16)
            plt.xlabel('Phase of Trade')
            plt.ylabel('Average Ratio')
            plt.ylim(0, 1)
            plt.legend(title='Type')
            plt.tight_layout()
            plt.savefig(os.path.join(plot_dir,
                                     f'avg_profit_loss_ratio_half_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()
        except Exception as e:
            logging.error(f"Error : {e}")

Python
avg_profit_ratio_first_half = df_trades['Profit_Time_Ratio_First_Half'].mean(): Вычисляем среднюю долю времени в прибыли за первую половину сделки.

avg_loss_ratio_first_half = df_trades['Loss_Time_Ratio_First_Half'].mean(): Вычисляем среднюю долю времени в убытке за первую половину сделки.

avg_profit_ratio_second_half = df_trades['Profit_Time_Ratio_Second_Half'].mean(): Вычисляем среднюю долю времени в прибыли за вторую половину сделки.

avg_loss_ratio_second_half = df_trades['Loss_Time_Ratio_Second_Half'].mean(): Вычисляем среднюю долю времени в убытке за вторую половину сделки.

ratios = pd.DataFrame(...): Создаем DataFrame для соотношений прибыли и убытков.

ratios_melted = ratios.melt(...): Преобразуем DataFrame в "длинный" формат для отрисовки.

plt.figure(figsize=(10, 6)): Создаем новое изображение.

sns.barplot(x='Phase', y='Ratio', hue='Type', data=ratios_melted, palette=['green', 'red']): Строим столбчатую диаграмму средних соотношений.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Phase of Trade'): Устанавливаем подпись оси X.

plt.ylabel('Average Ratio'): Устанавливаем подпись оси Y.

plt.ylim(0, 1): Устанавливаем пределы оси Y.

plt.legend(title='Type'): Включаем легенду.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

except Exception as e:: Перехватываем все ошибки.

logging.error(f"Error : {e}"): Выводим сообщение об ошибке в лог.

def plot_trade_execution(test, trades_list, symbol, timeframe_name, prediction_method, plot_dir, data_source):
        try:
            data_ohlc = test[['Open', 'High', 'Low', 'Close']].copy()
            df_trades = pd.DataFrame(trades_list)

            if df_trades.empty:
                logging.warning(f"No trades to plot for {symbol} ({timeframe_name}) -  {prediction_method}_({data_source}).")
                return

            df_trades['Exit_Reason_Detailed'] = df_trades.apply(
                lambda row: 'Opposite Signal (Profit)' if row['Exit_Reason'] == 'Opposite Signal' and row['Points'] > 0
                else ('Opposite Signal (Loss)' if row['Exit_Reason'] == 'Opposite Signal' and row['Points'] <= 0
                      else row['Exit_Reason']),
                axis=1
            )

            plt.figure(figsize=(14, 7))
            plt.plot(data_ohlc.index, data_ohlc['Close'], label='Close Price', color='black', linewidth=0.5)

            exit_reason_styles = {
                'TP': {'color': EXIT_REASON_PALETTE['TP'], 'marker': 'o'},
                'SL': {'color': EXIT_REASON_PALETTE['SL'], 'marker': 'x'},
                'Close at End': {'color': EXIT_REASON_PALETTE['Close at End'], 'marker': 's'},
                'Opposite Signal (Profit)': {'color': EXIT_REASON_PALETTE['Opposite Signal (Profit)'], 'marker': '^'},
                'Opposite Signal (Loss)': {'color': EXIT_REASON_PALETTE['Opposite Signal (Loss)'], 'marker': 'v'},
            }

            for reason, style in exit_reason_styles.items():
                reason_trades = df_trades[df_trades['Exit_Reason_Detailed'] == reason]
                if not reason_trades.empty:
                    plt.scatter(reason_trades['Exit_Date'], reason_trades['Exit_Price'],
                                color=style['color'],
                                marker=style['marker'],
                                label=f'Exit by {reason}')

            plt.title(f'Trade Exits on Price Chart\n{symbol} ({timeframe_name}) -  {prediction_method}_({data_source})')
            plt.xlabel('Date')
            plt.ylabel('Price')
            plt.legend(title='Exit Reason')
            plt.grid(True)
            plt.savefig(os.path.join(plot_dir, f'trade_exits_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()
        except Exception as e:
            logging.error(f"Error plotting trade execution: {e}")

Python
def plot_trade_execution(test, trades_list, symbol, timeframe_name, prediction_method, plot_dir, data_source):: Определяем функцию для отрисовки точек закрытия сделок на графике цены.

test: DataFrame с тестовыми данными.

trades_list: Список сделок.

symbol: Символ валютной пары.

timeframe_name: Имя таймфрейма.

prediction_method: Метод предсказания.

plot_dir: Директория для сохранения графиков

data_source: Источник данных

try ... except Exception as e:: Обработка возможных исключений.

data_ohlc = test[['Open', 'High', 'Low', 'Close']].copy(): Получаем данные OHLC.

df_trades = pd.DataFrame(trades_list): Преобразуем список сделок в DataFrame.

if df_trades.empty:: Проверяем, есть ли сделки для отрисовки.

df_trades['Exit_Reason_Detailed'] = df_trades.apply(...): Создаём столбец с детальной информацией о причинах закрытия позиций.

plt.figure(figsize=(14, 7)): Создаём новое изображение.

plt.plot(data_ohlc.index, data_ohlc['Close'], ...): Строим график цен закрытия.

exit_reason_styles = { ... }: Определяем стили для маркеров причин закрытия позиций.

for reason, style in exit_reason_styles.items():: Итерируемся по причинам закрытия позиции.

reason_trades = df_trades[df_trades['Exit_Reason_Detailed'] == reason]: Получаем DataFrame только для текущей причины закрытия.

if not reason_trades.empty:: Проверяем, есть ли сделки для отрисовки.

plt.scatter(reason_trades['Exit_Date'], reason_trades['Exit_Price'], ...): Строим точечную диаграмму для точек закрытия позиций.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Date'): Устанавливаем подпись оси X.

plt.ylabel('Price'): Устанавливаем подпись оси Y.

plt.legend(title='Exit Reason'): Включаем легенду.

plt.grid(True): Включаем сетку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

except Exception as e:: Перехватываем все ошибки.

logging.error(f"Error plotting trade execution: {e}"): Выводим сообщение об ошибке в лог.

def plot_predicted_vs_actual(test, symbol, timeframe_name, prediction_method, plot_dir, data_source):
        try:
            plt.figure(figsize=(14, 7))
            plt.plot(test.index, test['Close'], label='Actual Close Price', color='blue')
            plt.plot(test.index, test[prediction_method], label='Predicted Close Price', color='red', linewidth=0.5)
            plt.title(
                f'Actual vs Predicted Close Price for {symbol} ({timeframe_name})\nPrediction Method:  {prediction_method}_({data_source})')
            plt.xlabel('Date')
            plt.ylabel('Price')
            plt.legend()
            plt.grid(True)
            plt.tight_layout()
            plt.savefig(
                os.path.join(plot_dir, f'actual_vs_predicted_close_{symbol}_{timeframe_name}_{prediction_method}_({data_source}).png'))
            plt.close()
        except Exception as e:
            logging.error(f"Error plotting Actual vs Predicted Close Price: {e}")

Python
def plot_predicted_vs_actual(test, symbol, timeframe_name, prediction_method, plot_dir, data_source):: Определяем функцию для отрисовки графика, сравнивающего фактические и предсказанные цены закрытия.

test: DataFrame с тестовыми данными.

symbol: Символ валютной пары.

timeframe_name: Имя таймфрейма.

prediction_method: Метод предсказания.

plot_dir: Директория для сохранения графиков

data_source: Источник данных

try ... except Exception as e:: Обработка возможных исключений.

plt.figure(figsize=(14, 7)): Создаем новое изображение.

plt.plot(test.index, test['Close'], ...): Строим график фактической цены закрытия.

plt.plot(test.index, test[prediction_method], ...): Строим график предсказанной цены закрытия.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Date'): Устанавливаем подпись оси X.

plt.ylabel('Price'): Устанавливаем подпись оси Y.

plt.legend(): Включаем легенду.
* plt.grid(True): Включаем сетку.
* plt.tight_layout(): Уплотняем разметку.
* plt.savefig(...): Сохраняем график в файл.
* plt.close(): Закрываем изображение.

except Exception as e:: Перехватываем все ошибки.
*

86.2s
Продолжайте отвечать, включая последнюю строку ответа.

Хорошо, продолжаем разбор кода с последней строки предыдущего ответа:

except Exception as e:
            logging.error(f"Error plotting Actual vs Predicted Close Price: {e}")

    if show_plots:
        def plot_sl_features_shap(sl_model, X_sl_scaled, feature_names, symbol, timeframe_name, plot_dir, data_source):
            try:
                explainer_sl = shap.TreeExplainer(sl_model)
                shap_values_sl = explainer_sl.shap_values(X_sl_scaled)

                plt.figure(figsize=(12, 8))
                shap.summary_plot(shap_values_sl, X_sl_scaled, feature_names=feature_names, show=False)
                plt.title(f'SHAP Summary Plot for SL Features\n{symbol} ({timeframe_name}) - {data_source}', fontsize=16)
                plt.tight_layout()
                plt.savefig(os.path.join(plot_dir, f'shap_sl_features_importance_{symbol}_{timeframe_name}_({data_source}).png'))
                plt.close()

                return shap_values_sl
            except Exception as e:
                logging.error(f"Error plotting SHAP values for SL features: {e}")
                return None

Python
logging.error(f"Error plotting Actual vs Predicted Close Price: {e}"): Выводим сообщение об ошибке в лог.

if show_plots:: Проверяем, включена ли отрисовка графиков.

def plot_sl_features_shap(sl_model, X_sl_scaled, feature_names, symbol, timeframe_name, plot_dir, data_source):: Определяем функцию для отрисовки SHAP summary plot для SL-модели.

sl_model: SL-модель (классификатор).

X_sl_scaled: Масштабированные данные для SL-модели.

feature_names: Названия фичей.

symbol: Символ валютной пары.

timeframe_name: Имя таймфрейма.

plot_dir: Директория для сохранения графиков.

data_source: Источник данных.

try ... except Exception as e:: Обработка возможных исключений.

explainer_sl = shap.TreeExplainer(sl_model): Создаем объект SHAP explainer для SL-модели.

shap_values_sl = explainer_sl.shap_values(X_sl_scaled): Вычисляем SHAP значения для SL-модели.

plt.figure(figsize=(12, 8)): Создаем новое изображение.

shap.summary_plot(shap_values_sl, X_sl_scaled, feature_names=feature_names, show=False): Отрисовываем SHAP summary plot.

plt.title(...): Устанавливаем заголовок.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

return shap_values_sl: Возвращаем SHAP значения.

except Exception as e:: Перехватываем все ошибки.

logging.error(f"Error plotting SHAP values for SL features: {e}"): Выводим сообщение об ошибке в лог.

return None: Возвращаем None в случае ошибки.

# --------------------- Genetic Algorithm Functions ---------------------
def random_window_ga():
    return random.choice(window_values_ga_list)

def random_multiplier_ga():
    return random.choice(multiplier_values_ga_list)

def random_tp_factor_ga():
    return random.choice(tp_factor_values_ga_list)

def random_sl_factor_ga():
    return random.choice(sl_factor_values_ga_list)

def random_sl_max_depth_ga():
    return random.choice(sl_max_depth_list)

def random_sl_learning_rate_ga():
    return random.choice(sl_learning_rate_list)

def random_sl_n_estimators_ga():
    return random.choice(sl_n_estimators_list)

Python
def random_window_ga():: Определяем функцию для случайного выбора значения окна.

return random.choice(window_values_ga_list): Возвращает случайно выбранное значение из списка окон.

def random_multiplier_ga():: Определяем функцию для случайного выбора значения мультипликатора.

return random.choice(multiplier_values_ga_list): Возвращает случайно выбранное значение из списка мультипликаторов.

def random_tp_factor_ga():: Определяем функцию для случайного выбора значения фактора тейк-профита.

return random.choice(tp_factor_values_ga_list): Возвращает случайно выбранное значение из списка факторов тейк-профита.

def random_sl_factor_ga():: Определяем функцию для случайного выбора значения фактора стоп-лосса.

return random.choice(sl_factor_values_ga_list): Возвращает случайно выбранное значение из списка факторов стоп-лосса.

def random_sl_max_depth_ga():: Определяем функцию для случайного выбора значения максимальной глубины дерева для SL.

return random.choice(sl_max_depth_list): Возвращает случайно выбранное значение из списка глубин.

def random_sl_learning_rate_ga():: Определяем функцию для случайного выбора значения скорости обучения для SL.

return random.choice(sl_learning_rate_list): Возвращает случайно выбранное значение из списка скоростей обучения.

def random_sl_n_estimators_ga():: Определяем функцию для случайного выбора количества деревьев для SL.

return random.choice(sl_n_estimators_list): Возвращает случайно выбранное значение из списка количества деревьев.

def custom_mutate(individual, indpb):
    mutation_mask = np.random.rand(len(individual)) < indpb
    for i, mutate in enumerate(mutation_mask):
        if mutate:
            if i == 0:
                new_window = random.choice(window_values_ga_list)
                if 10 <= new_window < 252:
                    individual[i] = new_window
            elif i == 1:
                individual[i] = random.choice(multiplier_values_ga_list)
            elif i == 2:
                individual[i] = random.choice(tp_factor_values_ga_list)
            elif i == 3:
                individual[i] = random.choice(sl_factor_values_ga_list)
            elif i == 4:
                individual[i] = random.choice(sl_max_depth_list)
            elif i == 5:
                individual[i] = random.choice(sl_learning_rate_list)
            elif i == 6:
                individual[i] = random.choice(sl_n_estimators_list)
    return (individual,)

Python
def custom_mutate(individual, indpb):: Определяет функцию для мутации отдельных генов в индивиде.

individual: Индивид для мутации.

indpb: Вероятность мутации каждого гена.

mutation_mask = np.random.rand(len(individual)) < indpb: Создаем маску мутации (массив с булевыми значениями, указывающими какие гены нужно мутировать).

for i, mutate in enumerate(mutation_mask):: Итерируемся по всем генам, используя их индексы и значение в маске мутации.

if mutate:: Если ген нужно мутировать:

if i == 0:: Если это ген окна:

new_window = random.choice(window_values_ga_list): Случайно выбираем новое значение окна

if 10 <= new_window < 252:: Проверяем, чтобы новое значение не выходило за рамки допустимых границ.
* individual[i] = new_window: Устанавливаем новое значение окна.

elif i == 1:: Если это ген мультипликатора, выбираем случайное значение из списка мультипликаторов.

elif i == 2:: Если это ген тейк-профита, выбираем случайное значение из списка тейк-профита.

elif i == 3:: Если это ген стоп-лосса, выбираем случайное значение из списка стоп-лосса.

elif i == 4:: Если это ген глубины SL-модели, выбираем случайное значение из списка глубин.

elif i == 5:: Если это ген скорости обучения SL-модели, выбираем случайное значение из списка скоростей.

elif i == 6:: Если это ген количества деревьев SL-модели, выбираем случайное значение из списка деревьев.

return (individual,): Возвращаем изменённого индивида в виде кортежа.

def global_evaluate(individual, evaluator_instance, sl_features, ga_base_seed, backtest_data=None, data_source=None):
    try:
        if len(individual) != 7:
            logging.error(f"Expected 7 parameters, got {len(individual)}: {individual}")
            return tuple([1e-10] * len(evaluator_instance.prediction_columns))

        window, multiplier, tp_factor, sl_factor, sl_max_depth, sl_learning_rate, sl_n_estimators = map(float, individual)
        window = int(window)

        if window <= 0 or multiplier <= 0:
            logging.warning("Invalid parameter values. Returning minimal fitness.")
            return tuple([1e-10] * len(evaluator_instance.prediction_columns))

        sortino_ratios_no_sl = evaluator_instance.evaluate(
            [window, multiplier, tp_factor, sl_factor],
            classifier_sl=None,
            sl_threshold=0.7,
            backtest_data=backtest_data,
            data_source=data_source  # Ensure data_source is passed
        )

        trade_entries = []
        for trade in evaluator_instance.trades_dict.get('Ensemble_Predicted_Close_Future', []):
            entry_date = trade.get('Entry_Date')
            hit_sl = trade.get('Hit_SL')
            if entry_date is None or hit_sl is None:
                continue
            try:
                entry_features = evaluator_instance.df.loc[entry_date, sl_features].to_dict()
                trade_entries.append({'Hit_SL': hit_sl, **entry_features})
            except KeyError:
                continue

        df_trade_entries = pd.DataFrame(trade_entries)

        if not df_trade_entries.empty:
            df_trade_entries.fillna(method='ffill', inplace=True)
            df_trade_entries.fillna(method='bfill', inplace=True)

            X_sl_new = df_trade_entries[sl_features].values
            y_sl_new = df_trade_entries['Hit_SL'].values

            scaler_sl = MinMaxScaler()
            X_sl_scaled = scaler_sl.fit_transform(X_sl_new)

            try:
                X_train_sl, X_val_sl, y_train_sl, y_val_sl = train_test_split(
                    X_sl_scaled, y_sl_new, test_size=0.2, random_state=ga_base_seed, stratify=y_sl_new
                )
            except ValueError:
                X_train_sl, X_val_sl, y_train_sl, y_val_sl = train_test_split(
                    X_sl_scaled, y_sl_new, test_size=0.2, random_state=ga_base_seed
                )

Python
def global_evaluate(individual, evaluator_instance, sl_features, ga_base_seed, backtest_data=None, data_source=None):: Определяем функцию для оценки индивида с обучением и применением SL-модели.

individual: Индивид (список параметров).

evaluator_instance: Экземпляр класса StrategyEvaluator.

sl_features: Список фичей для SL-модели.

ga_base_seed: Зерно для случайного генератора.

backtest_data: Данные для бектестирования.

data_source: Источник данных.

try ... except Exception as e:: Обработка возможных исключений.
* if len(individual) != 7:: Проверяем, что длина списка параметров равна 7. Если не равна, выводим ошибку и возвращаем минимальный фитнес.
* window, multiplier, tp_factor, sl_factor, sl_max_depth, sl_learning_rate, sl_n_estimators = map(float, individual): Распаковываем параметры из индивида.
* window = int(window): Приводим значение окна к целому типу.
* if window <= 0 or multiplier <= 0:: Проверяем корректность значений окна и мультипликатора, если они неверны, возвращаем минимальный фитнес.
* sortino_ratios_no_sl = evaluator_instance.evaluate(...): Оцениваем стратегию без использования SL-модели.
* trade_entries = []: Создаём пустой список для хранения информации о сделках.

for trade in evaluator_instance.trades_dict.get('Ensemble_Predicted_Close_Future', []):: Итерируемся по всем сделкам, полученным от функции evaluate, без SL.

entry_date = trade.get('Entry_Date'): Получаем дату входа в сделку.
* hit_sl = trade.get('Hit_SL'): Получаем флаг срабатывания стоп-лосса.

if entry_date is None or hit_sl is None:: Проверяем наличие даты входа и флага.

try ... except KeyError:: Обрабатываем ошибки KeyError (если не удалось получить features для сделки из DataFrame).

entry_features = evaluator_instance.df.loc[entry_date, sl_features].to_dict(): Получаем фичи для SL-модели на основе даты входа из DataFrame.

trade_entries.append({'Hit_SL': hit_sl, **entry_features}): Добавляем в список данные по сделки и её фичи.
* df_trade_entries = pd.DataFrame(trade_entries): Преобразуем список сделок в DataFrame.

if not df_trade_entries.empty:: Проверяем, есть ли сделки для обучения SL.

df_trade_entries.fillna(method='ffill', inplace=True): Заполняем пропущенные значения.

df_trade_entries.fillna(method='bfill', inplace=True): Заполняем пропущенные значения.

X_sl_new = df_trade_entries[sl_features].values: Получаем фичи для SL-модели.
* y_sl_new = df_trade_entries['Hit_SL'].values: Получаем целевую переменную для SL-модели.

scaler_sl = MinMaxScaler(): Создаём экземпляр скейлера.

X_sl_scaled = scaler_sl.fit_transform(X_sl_new): Масштабируем фичи.

try ... except ValueError:: Обработка ошибок ValueError (если не удалось разбить на выборки).
* X_train_sl, X_val_sl, y_train_sl, y_val_sl = train_test_split(...): Разделяем данные на обучающую и валидационную выборки со стратификацией (если это возможно).

except ValueError:: Если стратификация невозможна
* X_train_sl, X_val_sl, y_train_sl, y_val_sl = train_test_split(...): Разделяем данные на обучающую и валидационную выборки без стратификации.

classifier_sl = xgb.XGBClassifier(
                objective='binary:logistic',
                n_estimators=int(sl_n_estimators),
                max_depth=int(sl_max_depth),
                learning_rate=sl_learning_rate,
                random_state=ga_base_seed,
                use_label_encoder=False,
                eval_metric='logloss',
                n_jobs=-1
            )

            classifier_sl.fit(X_train_sl, y_train_sl)
            joblib.dump(classifier_sl, 'sl_prediction_model.joblib')
            joblib.dump(scaler_sl, 'sl_scaler.joblib')

            scaler_X_selected_dict['Close_SL'] = scaler_sl

            sortino_ratios = evaluator_instance.evaluate(
                [window, multiplier, tp_factor, sl_factor],
                classifier_sl=classifier_sl,
                sl_threshold=0.7,
                backtest_data=backtest_data,
                data_source=data_source  # Ensure data_source is passed
            )

            return tuple(sortino_ratios)

Python
classifier_sl = xgb.XGBClassifier(...): Создаем экземпляр классификатора XGBoost для SL-модели.

classifier_sl.fit(X_train_sl, y_train_sl): Обучаем SL-модель.

joblib.dump(classifier_sl, 'sl_prediction_model.joblib'): Сохраняем обученную SL-модель.

joblib.dump(scaler_sl, 'sl_scaler.joblib'): Сохраняем обученный скейлер.

scaler_X_selected_dict['Close_SL'] = scaler_sl: Сохраняем скейлер.

sortino_ratios = evaluator_instance.evaluate(...): Оцениваем стратегию с использованием обученной SL-модели.

return tuple(sortino_ratios): Возвращаем коэффициенты Сортино.

logging.warning("No trades generated for this individual. Returning minimal fitness.")
        return tuple([1e-10] * len(evaluator_instance.prediction_columns))

    except Exception as e:
        logging.exception("Unexpected error in global_evaluate")
        return tuple([1e-10] * len(evaluator_instance.prediction_columns))

Python
logging.warning("No trades generated for this individual. Returning minimal fitness."): Если список сделок оказался пустым (не подобраны параметры), то выводим предупреждение и устанавливаем минимальный фитнес.

return tuple([1e-10] * len(evaluator_instance.prediction_columns)): Возвращаем минимальный фитнес.

except Exception as e:: Перехватываем все исключения.

logging.exception("Unexpected error in global_evaluate"): Выводим сообщение об ошибке.

return tuple([1e-10] * len(evaluator_instance.prediction_columns)): Возвращаем минимальный фитнес.

def run_ga(toolbox, ga_num_generations, ga_pop_size, label, patience=10, backtest_data=None, data_source=None):
    try:
        random.seed(ga_base_seed)
        np.random.seed(ga_base_seed)
        pop = toolbox.population(n=ga_pop_size)

        logging.info(f"Initial Population Size: {len(pop)}")
        logging.info(f"Starting genetic algorithm optimization for {label}...")

        stats = tools.Statistics(lambda ind: ind.fitness.values)
        stats.register('max', np.max)
        stats.register('avg', np.mean)

        hof = tools.HallOfFame(1)
        logbook = tools.Logbook()

        best_fitness = -np.inf
        patience_counter = patience

Python
def run_ga(toolbox, ga_num_generations, ga_pop_size, label, patience=10, backtest_data=None, data_source=None):: Определяем функцию для запуска генетического алгоритма.

toolbox: Набор инструментов для генетического алгоритма.

ga_num_generations: Количество поколений.

ga_pop_size: Размер популяции.

label: Метка для логгирования.

patience: Параметр для ранней остановки.

backtest_data: Данные для бектестирования.

data_source: Источник данных

try ... except Exception as e:: Обработка возможных исключений.
* random.seed(ga_base_seed): Устанавливаем зерно для случайного генератора.
* np.random.seed(ga_base_seed): Устанавливаем зерно для случайного генератора numpy.
* pop = toolbox.population(n=ga_pop_size): Создаем начальную популяцию.
* logging.info(f"Initial Population Size: {len(pop)}"): Выводим сообщение с размером популяции.
* logging.info(f"Starting genetic algorithm optimization for {label}..."): Выводим сообщение о начале оптимизации.
* stats = tools.Statistics(lambda ind: ind.fitness.values): Создаем объект для сбора статистики.
* stats.register('max', np.max): Регистрируем функцию для вычисления максимума.
* stats.register('avg', np.mean): Регистрируем функцию для вычисления среднего.
* hof = tools.HallOfFame(1): Создаем объект для хранения лучшего индивида.
* logbook = tools.Logbook(): Создаем объект для хранения лога.
* best_fitness = -np.inf: Инициализируем лучшее значение фитнеса в -inf.
* patience_counter = patience: Инициализируем счетчик patience для ранней остановки.

gen = 0
        while gen < ga_num_generations:

            offspring = algorithms.varAnd(pop, toolbox, cxpb=ga_cxpb, mutpb=ga_mutpb)

            #logging.info(f"Generation {gen}: Offspring Size: {len(offspring)}")

            fits = [toolbox.evaluate(ind, backtest_data=backtest_data, data_source=data_source) for ind in offspring]

            for fit, ind in zip(fits, offspring):
                ind.fitness.values = fit

            if not offspring:
                logging.error(f"Generation {gen}: Offspring population is empty.")
                break

            pop = toolbox.select(offspring, k=len(pop))
            hof.update(pop)

            #logging.info(f"Generation {gen}: Population Size after selection: {len(pop)}")

            record = stats.compile(pop)
            logbook.record(gen=gen, **record)
            logging.info(f"{label} - Generation {gen}: Max {record['max']}, Avg {record['avg']}")

Python
gen = 0: Инициализируем счетчик поколений.

while gen < ga_num_generations:: Запускаем основной цикл генетического алгоритма, который выполняется пока количество поколений меньше чем ga_num_generations.

offspring = algorithms.varAnd(pop, toolbox, cxpb=ga_cxpb, mutpb=ga_mutpb): Создаем новое поколение скрещиванием и мутацией.

fits = [toolbox.evaluate(ind, backtest_data=backtest_data, data_source=data_source) for ind in offspring]: Оцениваем фитнес каждого индивида в новом поколении.

for fit, ind in zip(fits, offspring): ind.fitness.values = fit: Присваиваем значения фитнеса каждому индивиду.

if not offspring:: Проверяем, что популяция потомков не пустая.

logging.error(f"Generation {gen}: Offspring population is empty."): Если пустая, выводим сообщение об ошибке и выходим из цикла.

break: Прерываем цикл while.

pop = toolbox.select(offspring, k=len(pop)): Выбираем индивидов для следующего поколения.

hof.update(pop): Обновляем HallOfFame лучшим индивидом.

record = stats.compile(pop): Собираем статистику по текущему поколению.

logbook.record(gen=gen, **record): Записываем данные в журнал.

logging.info(f"{label} - Generation {gen}: Max {record['max']}, Avg {record['avg']}"): Выводим сообщение о максимальном и среднем фитнесе.

current_max = pow(record['max'], 0.6) * pow(record['avg'], 0.4)
            if current_max > best_fitness:
                best_fitness = current_max
                patience_counter = patience
                logging.info(f"Generation {gen}: Improvement found. Resetting patience.")
            else:
                patience_counter -= 1
                logging.info(f"Generation {gen}: No improvement. Patience remaining: {patience_counter}")

            if gen == ga_num_generations - 1 and patience_counter >= patience * 0.5:
                old_ga_num_generations = ga_num_generations
                ga_num_generations += patience
                patience_counter = patience - (ga_num_generations / 10)
                logging.info(
                    f"Maximum generations reached and no improvement for GA, increasing generations to {ga_num_generations} (from {old_ga_num_generations})")
                # No continue needed here, the while loop condition will handle it

            if patience_counter <= 0:
                logging.info(f"Early stopping triggered after {gen + 1} generations for {label}.")
                break

            gen += 1  # Important: Increment gen at the end of the loop

Python
current_max = pow(record['max'], 0.6) * pow(record['avg'], 0.4): Вычисляем критерий улучшения.

if current_max > best_fitness:: Проверяем, улучшился ли фитнес.

best_fitness = current_max: Если да, то обновляем лучшее значение фитнеса.

patience_counter = patience: Сбрасываем счетчик терпения.

logging.info(f"Generation {gen}: Improvement found. Resetting patience."): Выводим сообщение.

else:: Если фитнес не улучшился:

patience_counter -= 1: Уменьшаем счетчик терпения на 1.

logging.info(f"Generation {gen}: No improvement. Patience remaining: {patience_counter}"): Выводим сообщение.

if gen == ga_num_generations - 1 and patience_counter >= patience * 0.5:: Если достигнуто максимальное количество поколений и счетчик терпения больше или равен половине значения терпения, то:

old_ga_num_generations = ga_num_generations: Запоминаем старое количество поколений.

ga_num_generations += patience: Увеличиваем количество поколений.

patience_counter = patience - (ga_num_generations / 10): Уменьшаем счетчик терпения.

logging.info(...): Выводим сообщение об увеличении поколений.

if patience_counter <= 0:: Если терпение закончилось (счетчик терпения меньше или равен нулю):

logging.info(f"Early stopping triggered after {gen + 1} generations for {label}."): Выводим сообщение о ранней остановке.

break: Прерываем цикл while.

gen += 1: Увеличиваем счетчик поколений на 1.

logging.info(f"Final Population Size: {len(pop)}")
        return hof[0] if len(hof) > 0 else None

    except Exception as e:
        logging.error(f"Error during GA optimization: {e}")
        return None

Python
logging.info(f"Final Population Size: {len(pop)}"): Выводим сообщение с конечным размером популяции.

return hof[0] if len(hof) > 0 else None: Возвращаем лучшего индивида из HallOfFame, если он есть, иначе возвращаем None.

except Exception as e:: Перехватываем все исключения.

logging.error(f"Error during GA optimization: {e}"): Выводим сообщение об ошибке в лог.

return None: Возвращаем None.

def create_toolbox(evaluator_instance, sl_features, ga_base_seed):
    def evaluate_with_backtest(ind, backtest_data=None, data_source=None):
        return global_evaluate(ind, evaluator_instance=evaluator_instance, sl_features=sl_features, ga_base_seed=ga_base_seed, backtest_data=backtest_data, data_source=data_source)

    toolbox = base.Toolbox()
    toolbox.register('window', random_window_ga)
    toolbox.register('multiplier', random_multiplier_ga)
    toolbox.register('tp_factor', random_tp_factor_ga)
    toolbox.register('sl_factor', random_sl_factor_ga)
    toolbox.register('sl_max_depth', random_sl_max_depth_ga)
    toolbox.register('sl_learning_rate', random_sl_learning_rate_ga)
    toolbox.register('sl_n_estimators', random_sl_n_estimators_ga)
    toolbox.register('individual', tools.initCycle, creator.Individual,
                     (toolbox.window, toolbox.multiplier, toolbox.tp_factor, toolbox.sl_factor,
                      toolbox.sl_max_depth, toolbox.sl_learning_rate, toolbox.sl_n_estimators), n=1)
    toolbox.register('population', tools.initRepeat, list, toolbox.individual)
    toolbox.register('mate', tools.cxUniform, indpb=ga_mate_indpb)
    toolbox.register('mutate', custom_mutate, indpb=ga_mutate_indpb)
    toolbox.register('select', tools.selTournament, tournsize=3)
    toolbox.register('evaluate', evaluate_with_backtest)
    return toolbox

Python
def create_toolbox(evaluator_instance, sl_features, ga_base_seed):: Определяем функцию для создания объекта toolbox для генетического алгоритма.

evaluator_instance: Экземпляр класса StrategyEvaluator.

sl_features: Список фичей для SL-модели.

ga_base_seed: Зерно для случайного генератора.

def evaluate_with_backtest(ind, backtest_data=None, data_source=None):: Внутренняя функция для оценки с бэктестом.

return global_evaluate(...): Возвращаем результат функции global_evaluate.

toolbox = base.Toolbox(): Создаём экземпляр toolbox.

toolbox.register('window', random_window_ga): Регистрируем функцию для случайного выбора окна.

toolbox.register('multiplier', random_multiplier_ga): Регистрируем функцию для случайного выбора мультипликатора.

toolbox.register('tp_factor', random_tp_factor_ga): Регистрируем функцию для случайного выбора фактора тейк-профита.

toolbox.register('sl_factor', random_sl_factor_ga): Регистрируем функцию для случайного выбора фактора стоп-лосса.

toolbox.register('sl_max_depth', random_sl_max_depth_ga): Регистрируем функцию для случайного выбора глубины SL-модели.

**`toolbox.register('sl_learning

83.0s
Продолжайте отвечать, включая последнюю строку ответа.

Хорошо, продолжаем разбор кода с последней строки предыдущего ответа:

toolbox.register('sl_learning_rate', random_sl_learning_rate_ga)
    toolbox.register('sl_n_estimators', random_sl_n_estimators_ga)
    toolbox.register('individual', tools.initCycle, creator.Individual,
                     (toolbox.window, toolbox.multiplier, toolbox.tp_factor, toolbox.sl_factor,
                      toolbox.sl_max_depth, toolbox.sl_learning_rate, toolbox.sl_n_estimators), n=1)
    toolbox.register('population', tools.initRepeat, list, toolbox.individual)
    toolbox.register('mate', tools.cxUniform, indpb=ga_mate_indpb)
    toolbox.register('mutate', custom_mutate, indpb=ga_mutate_indpb)
    toolbox.register('select', tools.selTournament, tournsize=3)
    toolbox.register('evaluate', evaluate_with_backtest)
    return toolbox

Python
toolbox.register('sl_learning_rate', random_sl_learning_rate_ga): Регистрируем функцию для случайного выбора скорости обучения для SL-модели.

toolbox.register('sl_n_estimators', random_sl_n_estimators_ga): Регистрируем функцию для случайного выбора количества деревьев для SL-модели.

toolbox.register('individual', tools.initCycle, creator.Individual, ...): Регистрируем функцию для создания индивидуального объекта (списка) с помощью случайных функций.

toolbox.register('population', tools.initRepeat, list, toolbox.individual): Регистрируем функцию для создания популяции, вызывая toolbox.individual несколько раз.

toolbox.register('mate', tools.cxUniform, indpb=ga_mate_indpb): Регистрируем функцию для скрещивания, используя cxUniform с вероятностью ga_mate_indpb.

toolbox.register('mutate', custom_mutate, indpb=ga_mutate_indpb): Регистрируем функцию для мутации, используя функцию custom_mutate с вероятностью ga_mutate_indpb.

toolbox.register('select', tools.selTournament, tournsize=3): Регистрируем функцию для отбора, используя турнирный отбор с размером турнира 3.

toolbox.register('evaluate', evaluate_with_backtest): Регистрируем функцию для оценки индивида, используя функцию evaluate_with_backtest.

return toolbox: Возвращаем созданный toolbox.

# --------------------- Main Function ---------------------
def main_function():
    print("Starting main function...")

    if not mt5.initialize():
        logging.error("Failed to initialize MT5.")
        print("Failed to initialize MT5.")
        return
    print("MT5 initialized successfully.")

    total_cores = cpu_count()
    cpu_cores = min(num_cores_to_use, total_cores)
    logging.info(f"Using {cpu_cores} cores for multiprocessing.")

    os.makedirs(plot_base_dir, exist_ok=True)

Python
def main_function():: Определяем основную функцию программы.

print("Starting main function..."): Выводим сообщение о начале работы функции.

if not mt5.initialize():: Пытаемся инициализировать MT5.

logging.error("Failed to initialize MT5."): Если инициализация не удалась, то выводим сообщение об ошибке в лог.

print("Failed to initialize MT5."): Выводим сообщение об ошибке в консоль.

return: Завершаем выполнение функции.

print("MT5 initialized successfully."): Выводим сообщение об успешной инициализации MT5.

total_cores = cpu_count(): Получаем количество доступных ядер процессора.

cpu_cores = min(num_cores_to_use, total_cores): Выбираем минимальное количество между заданным и доступным количеством ядер.

logging.info(f"Using {cpu_cores} cores for multiprocessing."): Выводим сообщение о количестве используемых ядер.

os.makedirs(plot_base_dir, exist_ok=True): Создаём базовую директорию для графиков, если она не существует.

for timeframe in timeframes_to_process:
        timeframe_name = timeframes_dict.get(timeframe, 'UNKNOWN')
        logging.info(f"\nProcessing timeframe: {timeframe_name}")

        data_dict = {}

        for selected_pair in currency_pairs:
            logging.info(f"\nLoading and processing data for pair: {selected_pair}")
            data = get_mt5_data(selected_pair, timeframe, start_date, end_date)
            if data.empty:
                logging.warning(f"Data for {selected_pair} in timeframe {timeframe_name} is empty. Skipping.")
                continue
            data_dict[selected_pair] = data

            symbol = my_directory if my_directory else selected_pair
            plot_dir_symbol_date = os.path.join(plot_base_dir, symbol, current_date)
            os.makedirs(plot_dir_symbol_date, exist_ok=True)

            top_pairs = [(selected_pair,)]

Python
for timeframe in timeframes_to_process:: Итерируемся по всем таймфреймам, заданным в timeframes_to_process.

timeframe_name = timeframes_dict.get(timeframe, 'UNKNOWN'): Получаем название текущего таймфрейма из словаря, или 'UNKNOWN'.

logging.info(f"\nProcessing timeframe: {timeframe_name}"): Выводим сообщение о начале обработки текущего таймфрейма.

data_dict = {}: Создаём пустой словарь для хранения данных по парам.

for selected_pair in currency_pairs:: Итерируемся по всем валютным парам, заданным в currency_pairs.

logging.info(f"\nLoading and processing data for pair: {selected_pair}"): Выводим сообщение о начале загрузки данных для текущей пары.

data = get_mt5_data(selected_pair, timeframe, start_date, end_date): Загружаем данные для текущей пары и таймфрейма.

if data.empty:: Проверяем, что данные загружены, если нет:

logging.warning(f"Data for {selected_pair} in timeframe {timeframe_name} is empty. Skipping."): Выводим сообщение, что данные пусты и пропускаем пару.

continue: Переходим к следующей итерации цикла.

data_dict[selected_pair] = data: Сохраняем загруженные данные.

symbol = my_directory if my_directory else selected_pair: Устанавливаем имя каталога для сохранения данных, если он задан, в противном случае используем название пары.

plot_dir_symbol_date = os.path.join(plot_base_dir, symbol, current_date): Формируем путь к каталогу для сохранения результатов.

os.makedirs(plot_dir_symbol_date, exist_ok=True): Создаем каталоги для сохранения графиков.

top_pairs = [(selected_pair,)]: Создаем список пар, для которых будет производиться обработка (здесь используется только текущая пара).

for pair in top_pairs:
                symbol = pair[0]
                logging.info(f"\nOptimizing strategy for pair: {symbol}")
                df = data_dict[symbol].copy()

Python
for pair in top_pairs:: Итерируемся по парам.

symbol = pair[0]: Получаем символ текущей пары.

logging.info(f"\nOptimizing strategy for pair: {symbol}"): Выводим сообщение о начале оптимизации для текущей пары.

df = data_dict[symbol].copy(): Копируем DataFrame с данными.

# Calculate 'Ask' and 'Bid'
                symbol_info = mt5.symbol_info(symbol)
                if symbol_info is None:
                    logging.error(f"Cannot retrieve symbol info for {symbol}. Using default digits=5.")
                    point = 0.00001
                    spread = 0
                    df['Bid'] = df['Close']
                    df['Ask'] = df['Close'] + spread
                else:
                    point = symbol_info.point
                    spread = symbol_info.spread * point
                    df['Bid'] = df['Close']
                    df['Ask'] = df['Close'] + (symbol_info.spread * point)  # Corrected from 'df['Spread']'

Python
symbol_info = mt5.symbol_info(symbol): Получаем информацию о символе из MT5.

if symbol_info is None:: Если не удалось получить информацию о символе:

logging.error(f"Cannot retrieve symbol info for {symbol}. Using default digits=5."): Выводим сообщение об ошибке.

point = 0.00001: Устанавливаем значения point по умолчанию.

spread = 0: Устанавливаем значения spread по умолчанию.

df['Bid'] = df['Close']: Устанавливаем Bid равным Close по умолчанию.

df['Ask'] = df['Close'] + spread: Устанавливаем Ask равным Close + spread.

else:: Если информация о символе получена:

point = symbol_info.point: Получаем point из информации о символе.

spread = symbol_info.spread * point: Вычисляем spread из информации о символе.

df['Bid'] = df['Close']: Устанавливаем Bid равным Close.

df['Ask'] = df['Close'] + (symbol_info.spread * point): Вычисляем Ask на основе Close и spread .

# Feature Engineering
                lag_features = ['Close', 'High', 'Low', 'Open']
                lags = range(1, 6)
                for feature in lag_features:
                    for lag in lags:
                        df[f"{feature}_Lag_{lag}"] = df[feature].shift(lag)

                df['High_Future'] = df['High'].shift(-1)
                df['Low_Future'] = df['Low'].shift(-1)
                df['Close_Future'] = df['Close'].shift(-1)
                df.dropna(inplace=True)

                for price_col in ['Close', 'High', 'Low']:
                    transformations = apply_transformations(df[price_col])
                    best_transform_name, best_transform_series = select_best_transformation(df[price_col],
                                                                                            transformations)
                    best_transform_series = replace_inf_nan(best_transform_series)
                    df[f"{price_col}_Stationary"] = best_transform_series

Python
# Feature Engineering: Комментарий, что далее идёт блок создания новых признаков.

lag_features = ['Close', 'High', 'Low', 'Open']: Задаём список столбцов, для которых будем вычислять лаги.

lags = range(1, 6): Задаём диапазон для величины лагов.

for feature in lag_features:: Итерируемся по всем фичам, которые будем лагировать.

for lag in lags:: Итерируемся по всем значениям лага.

df[f"{feature}_Lag_{lag}"] = df[feature].shift(lag): Создаем новый столбец с лагом.

df['High_Future'] = df['High'].shift(-1): Создаем столбец со значением High, смещенным на 1 бар вперед.

df['Low_Future'] = df['Low'].shift(-1): Создаем столбец со значением Low, смещенным на 1 бар вперед.

df['Close_Future'] = df['Close'].shift(-1): Создаем столбец со значением Close, смещенным на 1 бар вперед.

df.dropna(inplace=True): Удаляем все строки, в которых есть NaN значения.

for price_col in ['Close', 'High', 'Low']:: Итерируемся по столбцам цены.

transformations = apply_transformations(df[price_col]): Применяем трансформацию к столбцу.

best_transform_name, best_transform_series = select_best_transformation(df[price_col], transformations): Выбираем лучшее преобразование для столбца.

best_transform_series = replace_inf_nan(best_transform_series): Заменяем inf и nan значения в трансформированном ряде.

df[f"{price_col}_Stationary"] = best_transform_series: Добавляем новый столбец в DataFrame с преобразованным рядом.

df['Range'] = df['High'] - df['Low']
                df['Close_Open'] = df['Close'] - df['Open']
                df['High_Close_Prev'] = df['High'] - df['Close'].shift(1)
                df['Low_Close_Prev'] = df['Low'] - df['Close'].shift(1)

Python
df['Range'] = df['High'] - df['Low']: Создаём столбец с разницей между максимальной и минимальной ценами.

df['Close_Open'] = df['Close'] - df['Open']: Создаём столбец с разницей между ценой закрытия и открытия.

df['High_Close_Prev'] = df['High'] - df['Close'].shift(1): Создаём столбец с разницей между текущей максимальной ценой и предыдущей ценой закрытия.

df['Low_Close_Prev'] = df['Low'] - df['Close'].shift(1): Создаём столбец с разницей между текущей минимальной ценой и предыдущей ценой закрытия.

atr = AverageTrueRange(high=df['High'], low=df['Low'], close=df['Close'], window=14)
                df['ATR'] = atr.average_true_range()

                rsi = RSIIndicator(close=df['Close'], window=14)
                df['RSI'] = rsi.rsi()

                bb = BollingerBands(close=df['Close'], window=20, window_dev=2)
                df['BB_High'] = bb.bollinger_hband()
                df['BB_Low'] = bb.bollinger_lband()
                df['BB_Mid'] = bb.bollinger_mavg()
                df['BB_Width'] = bb.bollinger_wband()

                macd = MACD(close=df['Close'], window_slow=26, window_fast=12, window_sign=9)
                df['MACD'] = macd.macd()
                df['MACD_Signal'] = macd.macd_signal()
                df['MACD_Diff'] = macd.macd_diff()

Python
atr = AverageTrueRange(high=df['High'], low=df['Low'], close=df['Close'], window=14): Создаём объект для расчета ATR.

df['ATR'] = atr.average_true_range(): Рассчитываем ATR и добавляем в DataFrame.

rsi = RSIIndicator(close=df['Close'], window=14): Создаём объект для расчета RSI.

df['RSI'] = rsi.rsi(): Рассчитываем RSI и добавляем в DataFrame.

bb = BollingerBands(close=df['Close'], window=20, window_dev=2): Создаём объект для расчета полос Боллинджера.

df['BB_High'] = bb.bollinger_hband(): Рассчитываем верхнюю полосу Боллинджера и добавляем в DataFrame.

df['BB_Low'] = bb.bollinger_lband(): Рассчитываем нижнюю полосу Боллинджера и добавляем в DataFrame.

df['BB_Mid'] = bb.bollinger_mavg(): Рассчитываем среднюю полосу Боллинджера и добавляем в DataFrame.

df['BB_Width'] = bb.bollinger_wband(): Рассчитываем ширину полосы Боллинджера и добавляем в DataFrame.

macd = MACD(close=df['Close'], window_slow=26, window_fast=12, window_sign=9): Создаём объект для расчета MACD.

df['MACD'] = macd.macd(): Рассчитываем MACD и добавляем в DataFrame.

df['MACD_Signal'] = macd.macd_signal(): Рассчитываем сигнальную линию MACD и добавляем в DataFrame.

df['MACD_Diff'] = macd.macd_diff(): Рассчитываем гистограмму MACD и добавляем в DataFrame.

stochastic = StochasticOscillator(high=df['High'], low=df['Low'], close=df['Close'],
                                                  window=14, smooth_window=3)
                df['Stochastic'] = stochastic.stoch()
                df['Stochastic_Signal'] = stochastic.stoch_signal()

                cci = CCIIndicator(high=df['High'], low=df['Low'], close=df['Close'], window=20)
                df['CCI'] = cci.cci()

                adx = ADXIndicator(high=df['High'], low=df['Low'], close=df['Close'], window=14)
                df['ADX'] = adx.adx()
                df['ADX_Pos'] = adx.adx_pos()
                df['ADX_Neg'] = adx.adx_neg()

                ema = EMAIndicator(close=df['Close'], window=14)
                df['EMA_14'] = ema.ema_indicator()

                df['SMA_14'] = df['Close'].rolling(window=14).mean()

                df['DayOfWeek'] = df.index.dayofweek
                df['WeekOfYear'] = df.index.isocalendar().week.astype(int)
                df['Month'] = df.index.month

Python
stochastic = StochasticOscillator(high=df['High'], low=df['Low'], close=df['Close'], window=14, smooth_window=3): Создаём объект для расчета стохастического осциллятора.

df['Stochastic'] = stochastic.stoch(): Рассчитываем стохастик и добавляем в DataFrame.

df['Stochastic_Signal'] = stochastic.stoch_signal(): Рассчитываем сигнальную линию стохастика и добавляем в DataFrame.

cci = CCIIndicator(high=df['High'], low=df['Low'], close=df['Close'], window=20): Создаём объект для расчета CCI.

df['CCI'] = cci.cci(): Рассчитываем CCI и добавляем в DataFrame.

adx = ADXIndicator(high=df['High'], low=df['Low'], close=df['Close'], window=14): Создаём объект для расчета ADX.

df['ADX'] = adx.adx(): Рассчитываем ADX и добавляем в DataFrame.

df['ADX_Pos'] = adx.adx_pos(): Рассчитываем ADX+ и добавляем в DataFrame.

df['ADX_Neg'] = adx.adx_neg(): Рассчитываем ADX- и добавляем в DataFrame.

ema = EMAIndicator(close=df['Close'], window=14): Создаём объект для расчета EMA.

df['EMA_14'] = ema.ema_indicator(): Рассчитываем EMA и добавляем в DataFrame.

df['SMA_14'] = df['Close'].rolling(window=14).mean(): Рассчитываем SMA и добавляем в DataFrame.

df['DayOfWeek'] = df.index.dayofweek: Добавляем столбец с днём недели.

df['WeekOfYear'] = df.index.isocalendar().week.astype(int): Добавляем столбец с номером недели в году.

df['Month'] = df.index.month: Добавляем столбец с месяцем.

roc = ROCIndicator(close=df['Close'], window=14)
                df['ROC_14'] = roc.roc()

                df['Momentum_14'] = df['Close'] - df['Close'].shift(14)

                williams_r = WilliamsRIndicator(high=df['High'], low=df['Low'], close=df['Close'], lbp=14)
                df['WilliamsR'] = williams_r.williams_r()

                ichimoku = IchimokuIndicator(high=df['High'], low=df['Low'], window1=9, window2=26, window3=52)
                df['Ichimoku_A'] = ichimoku.ichimoku_a()
                df['Ichimoku_B'] = ichimoku.ichimoku_b()

                df['Corr_Close_RSI_14'] = df['Close'].rolling(window=14).corr(df['RSI'])

                df['Return_Lag_1'] = df['Close'].pct_change(1).shift(1)
                df['Return_Lag_2'] = df['Close'].pct_change(2).shift(1)
                df['Return_Lag_3'] = df['Close'].pct_change(3).shift(1)

                df['RSI_MACD'] = df['RSI'] * df['MACD']
                df['ATR_Close'] = df['ATR'] / df['Close']

                df.dropna(inplace=True)

Python
roc = ROCIndicator(close=df['Close'], window=14): Создаём объект для расчета ROC.

df['ROC_14'] = roc.roc(): Рассчитываем ROC и добавляем в DataFrame.

df['Momentum_14'] = df['Close'] - df['Close'].shift(14): Рассчитываем моментум и добавляем в DataFrame.

williams_r = WilliamsRIndicator(high=df['High'], low=df['Low'], close=df['Close'], lbp=14): Создаём объект для расчета Williams %R.

df['WilliamsR'] = williams_r.williams_r(): Рассчитываем Williams %R и добавляем в DataFrame.

ichimoku = IchimokuIndicator(high=df['High'], low=df['Low'], window1=9, window2=26, window3=52): Создаём объект для расчета Ichimoku.

df['Ichimoku_A'] = ichimoku.ichimoku_a(): Рассчитываем Ichimoku A и добавляем в DataFrame.

df['Ichimoku_B'] = ichimoku.ichimoku_b(): Рассчитываем Ichimoku B и добавляем в DataFrame.

df['Corr_Close_RSI_14'] = df['Close'].rolling(window=14).corr(df['RSI']): Рассчитываем корреляцию между ценой закрытия и RSI и добавляем в DataFrame.

df['Return_Lag_1'] = df['Close'].pct_change(1).shift(1): Рассчитываем процентное изменение цены с лагом 1 и добавляем в DataFrame.

df['Return_Lag_2'] = df['Close'].pct_change(2).shift(1): Рассчитываем процентное изменение цены с лагом 2 и добавляем в DataFrame.

df['Return_Lag_3'] = df['Close'].pct_change(3).shift(1): Рассчитываем процентное изменение цены с лагом 3 и добавляем в DataFrame.

df['RSI_MACD'] = df['RSI'] * df['MACD']: Создаём столбец путем произведения RSI и MACD.

df['ATR_Close'] = df['ATR'] / df['Close']: Создаём столбец путем деления ATR на цену закрытия.

df.dropna(inplace=True): Удаляем все строки, в которых есть NaN значения.

# Split data
                train = df[:split_date].copy()
                test = df[split_date:backtest_start_date]  # Removed .copy() here
                backtest = df[backtest_start_date:backtest_end_date]  # Removed .copy() here

Python
# Split data: Комментарий, что далее идет разбиение данных на обучающую, тестовую и бэктестовую выборки.

train = df[:split_date].copy(): Выделяем обучающую выборку.

test = df[split_date:backtest_start_date]: Выделяем тестовую выборку.

backtest = df[backtest_start_date:backtest_end_date]: Выделяем выборку для бектестирования.

df.fillna(method='ffill', inplace=True)
                df.fillna(method='bfill', inplace=True)

                close_mean = df['Close'].mean()
                close_std = df['Close'].std()
                df = df[np.abs(df['Close'] - close_mean) < 3 * close_std]

Python
df.fillna(method='ffill', inplace=True): Заполняем пропущенные значения.

df.fillna(method='bfill', inplace=True): Заполняем пропущенные значения.

close_mean = df['Close'].mean(): Вычисляем среднее значение цены закрытия.

close_std = df['Close'].std(): Вычисляем стандартное отклонение цены закрытия.

df = df[np.abs(df['Close'] - close_mean) < 3 * close_std]: Оставляем только те значения, которые не выходят за пределы трех стандартных отклонений от среднего, для того, чтобы отфильтровать выбросы.

feature_columns = [
                    'Open', 'High', 'Low', 'Close',
                    'Close_Lag_1', 'Close_Lag_2', 'Close_Lag_3', 'Close_Lag_4', 'Close_Lag_5',
                    'High_Lag_1', 'High_Lag_2', 'High_Lag_3', 'High_Lag_4', 'High_Lag_5',
                    'Low_Lag_1', 'Low_Lag_2', 'Low_Lag_3', 'Low_Lag_4', 'Low_Lag_5',
                    'Open_Lag_1', 'Open_Lag_2', 'Open_Lag_3', 'Open_Lag_4', 'Open_Lag_5',
                    'Range', 'Close_Open', 'High_Close_Prev', 'Low_Close_Prev',
                    'ATR', 'RSI',
                    'BB_High', 'BB_Low', 'BB_Mid', 'BB_Width',
                    'MACD', 'MACD_Signal', 'MACD_Diff',
                    'Stochastic', 'Stochastic_Signal',
                    'CCI',
                    'ADX', 'ADX_Pos', 'ADX_Neg',
                    'EMA_14', 'SMA_14',
                    'DayOfWeek', 'WeekOfYear', 'Month',
                    'ROC_14', 'Momentum_14', 'WilliamsR', 'Ichimoku_A', 'Ichimoku_B',
                    'Corr_Close_RSI_14',
                    'Return_Lag_1', 'Return_Lag_2', 'Return_Lag_3',
                    'RSI_MACD', 'ATR_Close',
                    'Close_Stationary', 'High_Stationary', 'Low_Stationary'
                ]

Python
feature_columns = [ ... ]: Список фичей для обучения моделей.

# Scaling
                scaler_X = MinMaxScaler()
                scaler_y_high = MinMaxScaler()
                scaler_y_low = MinMaxScaler()
                scaler_y_close = MinMaxScaler()

Python
# Scaling: Комментарий, что далее идет блок масштабирования.

scaler_X = MinMaxScaler(): Создаем объект MinMaxScaler для масштабирования признаков.

scaler_y_high = MinMaxScaler(): Создаем объект MinMaxScaler для масштабирования целевой переменной High.

scaler_y_low = MinMaxScaler(): Создаем объект MinMaxScaler для масштабирования целевой переменной Low.

scaler_y_close = MinMaxScaler(): Создаем объект MinMaxScaler для масштабирования целевой переменной Close.

features = train[feature_columns].values
                target_high = train[['High_Future']].values
                target_low = train[['Low_Future']].values
                target_close = train[['Close_Future']].values

                X_train = scaler_X.fit_transform(features)
                y_train_high = scaler_y_high.fit_transform(target_high)
                y_train_low = scaler_y_low.fit_transform(target_low)
                y_train_close = scaler_y_close.fit_transform(target_close)

Python
features = train[feature_columns].values: Получаем фичи из DataFrame в виде массива NumPy.

target_high = train[['High_Future']].values: Получаем целевую переменную High_Future в виде массива NumPy.

target_low = train[['Low_Future']].values: Получаем целевую переменную Low_Future в виде массива NumPy.

target_close = train[['Close_Future']].values: Получаем целевую переменную Close_Future в виде массива NumPy.

X_train = scaler_X.fit_transform(features): Масштабируем фичи и сохраняем скейлер.

y_train_high = scaler_y_high.fit_transform(target_high): Масштабируем целевую переменную High_Future и сохраняем скейлер.

y_train_low = scaler_y_low.fit_transform(target_low): Масштабируем целевую переменную Low_Future и сохраняем скейлер.

y_train_close = scaler_y_close.fit_transform(target_close): Масштабируем целевую переменную Close_Future и сохраняем скейлер.

test_features = test[feature_columns].values
                X_test = scaler_X.transform(test_features)
                y_test = test[['High_Future', 'Low_Future', 'Close_Future']].values

                backtest_features = backtest[feature_columns].values
                X_backtest = scaler_X.transform(backtest_features)
                y_backtest = backtest[['High_Future', 'Low_Future', 'Close_Future']].values

Python
test_features = test[feature_columns].values: Получаем фичи из тестового DataFrame в виде массива NumPy.

X_test = scaler_X.transform(test_features): Масштабируем тестовые фичи с помощью скейлера, обученного на тренировочной выборке.

y_test = test[['High_Future', 'Low_Future', 'Close_Future']].values: Получаем целевые переменные для тестовой выборки.

backtest_features = backtest[feature_columns].values: Получаем фичи из DataFrame для бэктестинга в виде массива NumPy.

X_backtest = scaler_X.transform(backtest_features): Масштабируем фичи для бэктестинга с помощью скейлера, обученного на тренировочной выборке.

y_backtest = backtest[['High_Future', 'Low_Future', 'Close_Future']].values: Получаем целевые переменные для бэктестовой выборки.

# Initial Model Training for feature importance
                models_initial = {}
                shap_values_dict = {}

                for col, scaler_y in zip(['High', 'Low', 'Close'],
                                         [scaler_y_high, scaler_y_low, scaler_y_close]):
                    model = xgb.XGBRegressor(
                        objective='reg:squarederror',
                        n_estimators=xgb_n_estimators,
                        max_depth=xgb_max_depth,
                        learning_rate=xgb_learning_rate,
                        random_state=xgb_random_state,
                        n_jobs=-1
                    )
                    model.fit(X_train, scaler_y.transform(train[[f"{col}_Future"]]).ravel())
                    models_initial[col] = model

                    explainer = shap.TreeExplainer(model)
                    shap_values = explainer.shap_values(X_train)
                    shap_values_dict[col] = shap_values

Python
**`# Initial Model Training

85.1s
Продолжайте отвечать, включая последнюю строку ответа.

Хорошо, продолжаем разбор кода с последней строки предыдущего ответа:

# Initial Model Training for feature importance
                models_initial = {}
                shap_values_dict = {}

                for col, scaler_y in zip(['High', 'Low', 'Close'],
                                         [scaler_y_high, scaler_y_low, scaler_y_close]):
                    model = xgb.XGBRegressor(
                        objective='reg:squarederror',
                        n_estimators=xgb_n_estimators,
                        max_depth=xgb_max_depth,
                        learning_rate=xgb_learning_rate,
                        random_state=xgb_random_state,
                        n_jobs=-1
                    )
                    model.fit(X_train, scaler_y.transform(train[[f"{col}_Future"]]).ravel())
                    models_initial[col] = model

                    explainer = shap.TreeExplainer(model)
                    shap_values = explainer.shap_values(X_train)
                    shap_values_dict[col] = shap_values

Python
# Initial Model Training for feature importance: Комментарий, что далее идет блок обучения моделей для определения важности признаков.

models_initial = {}: Создаем пустой словарь для хранения моделей.

shap_values_dict = {}: Создаем пустой словарь для хранения SHAP значений.

for col, scaler_y in zip(['High', 'Low', 'Close'], [scaler_y_high, scaler_y_low, scaler_y_close]):: Итерируемся по всем столбцам и соответствующим скейлерам.

model = xgb.XGBRegressor(...): Создаем экземпляр XGBoost регрессора.

model.fit(X_train, scaler_y.transform(train[[f"{col}_Future"]]).ravel()): Обучаем модель.

models_initial[col] = model: Сохраняем обученную модель в словарь.

explainer = shap.TreeExplainer(model): Создаём SHAP explainer для модели.

shap_values = explainer.shap_values(X_train): Вычисляем SHAP значения для тренировочных данных.

shap_values_dict[col] = shap_values: Сохраняем SHAP значения в словарь.

# Feature Selection based on SHAP
                selected_features_dict = {}
                excluded_features_dict = {}
                for col in ['High', 'Low', 'Close']:
                    shap_values = shap_values_dict[col]
                    shap_importance = np.abs(shap_values).mean(axis=0)
                    shap_importance_df = pd.DataFrame({
                        'Feature': feature_columns,
                        'SHAP Importance': shap_importance
                    })
                    shap_importance_df.sort_values(by='SHAP Importance', ascending=False, inplace=True)

                    N = 30
                    selected_features = shap_importance_df['Feature'].head(N).tolist()
                    excluded_features = shap_importance_df['Feature'].tail(len(feature_columns) - N).tolist()

                    selected_features_dict[col] = selected_features
                    excluded_features_dict[col] = excluded_features

Python
# Feature Selection based on SHAP: Комментарий, что далее идет блок отбора признаков на основе SHAP значений.

selected_features_dict = {}: Создаем пустой словарь для хранения отобранных признаков.

excluded_features_dict = {}: Создаем пустой словарь для хранения исключенных признаков.

for col in ['High', 'Low', 'Close']:: Итерируемся по столбцам.

shap_values = shap_values_dict[col]: Получаем SHAP значения для текущего столбца.

shap_importance = np.abs(shap_values).mean(axis=0): Вычисляем среднюю абсолютную важность признаков.

shap_importance_df = pd.DataFrame(...): Создаем DataFrame с важностью признаков.

shap_importance_df.sort_values(by='SHAP Importance', ascending=False, inplace=True): Сортируем DataFrame по убыванию важности.

N = 30: Устанавливаем количество отбираемых признаков.

selected_features = shap_importance_df['Feature'].head(N).tolist(): Получаем список N самых важных признаков.

excluded_features = shap_importance_df['Feature'].tail(len(feature_columns) - N).tolist(): Получаем список всех признаков, которые не вошли в топ N

selected_features_dict[col] = selected_features: Сохраняем отобранные фичи.

excluded_features_dict[col] = excluded_features: Сохраняем исключенные фичи.

if show_plots:
                        plt.figure(figsize=(12, 8))
                        sns.barplot(x='SHAP Importance', y='Feature', data=shap_importance_df.head(20))
                        plt.title(f'SHAP Feature Importance for {symbol} ({timeframe_name}) - {col}')
                        plt.xlabel('Mean Absolute SHAP Value')
                        plt.ylabel('Feature')
                        plt.tight_layout()
                        plt.savefig(os.path.join(plot_dir_symbol_date,
                                                 f'shap_feature_importance_{symbol}_{timeframe_name}_{col}.png'))
                        plt.close()

Python
if show_plots:: Проверяем, включена ли отрисовка графиков.

plt.figure(figsize=(12, 8)): Создаем новое изображение.

sns.barplot(x='SHAP Importance', y='Feature', data=shap_importance_df.head(20)): Строим столбчатую диаграмму SHAP важности признаков.

plt.title(...): Устанавливаем заголовок.

plt.xlabel('Mean Absolute SHAP Value'): Устанавливаем подпись оси X.

plt.ylabel('Feature'): Устанавливаем подпись оси Y.

plt.tight_layout(): Уплотняем разметку.

plt.savefig(...): Сохраняем график в файл.

plt.close(): Закрываем изображение.

X_train_selected_dict = {}
                X_test_selected_dict = {}
                X_backtest_selected_dict = {}
                scaler_X_selected_dict_local = {}

                for col in ['High', 'Low', 'Close']:
                    selected_features = selected_features_dict[col]
                    features_selected = train[selected_features].values
                    test_features_selected = test[selected_features].values
                    backtest_features_selected = backtest[selected_features].values

                    scaler_X_selected = MinMaxScaler()
                    X_train_selected = scaler_X_selected.fit_transform(features_selected)
                    X_test_selected = scaler_X_selected.transform(test_features_selected)
                    X_backtest_selected = scaler_X_selected.transform(backtest_features_selected)

                    X_train_selected_dict[col] = X_train_selected
                    X_test_selected_dict[col] = X_test_selected
                    X_backtest_selected_dict[col] = X_backtest_selected
                    scaler_X_selected_dict_local[col] = scaler_X_selected

Python
X_train_selected_dict = {}: Создаем пустой словарь для хранения масштабированных обучающих данных.

X_test_selected_dict = {}: Создаем пустой словарь для хранения масштабированных тестовых данных.

X_backtest_selected_dict = {}: Создаем пустой словарь для хранения масштабированных данных для бектеста.

scaler_X_selected_dict_local = {}: Создаем пустой словарь для хранения локальных масштабированных фичей

for col in ['High', 'Low', 'Close']:: Итерируемся по столбцам.

selected_features = selected_features_dict[col]: Получаем список отобранных признаков.

features_selected = train[selected_features].values: Получаем отобранные признаки из обучающей выборки.

test_features_selected = test[selected_features].values: Получаем отобранные признаки из тестовой выборки.

backtest_features_selected = backtest[selected_features].values: Получаем отобранные признаки из бэктестовой выборки.

scaler_X_selected = MinMaxScaler(): Создаём новый MinMaxScaler для масштабирования отобранных признаков.

X_train_selected = scaler_X_selected.fit_transform(features_selected): Масштабируем тренировочные данные.

X_test_selected = scaler_X_selected.transform(test_features_selected): Масштабируем тестовые данные.

X_backtest_selected = scaler_X_selected.transform(backtest_features_selected): Масштабируем данные для бэктеста.

X_train_selected_dict[col] = X_train_selected: Сохраняем масштабированные обучающие данные.

X_test_selected_dict[col] = X_test_selected: Сохраняем масштабированные тестовые данные.

X_backtest_selected_dict[col] = X_backtest_selected: Сохраняем масштабированные данные для бэктеста.

scaler_X_selected_dict_local[col] = scaler_X_selected: Сохраняем скейлер.

scaler_X_selected_dict.update(scaler_X_selected_dict_local)

                models = {}
                for idx, col in enumerate(['High', 'Low', 'Close']):
                    scaler_y = [scaler_y_high, scaler_y_low, scaler_y_close][idx]
                    X_train_selected = X_train_selected_dict[col]
                    model = xgb.XGBRegressor(
                        objective='reg:squarederror',
                        n_estimators=xgb_n_estimators,
                        max_depth=xgb_max_depth,
                        learning_rate=xgb_learning_rate,
                        random_state=xgb_random_state,
                        n_jobs=-1
                    )
                    model.fit(X_train_selected, scaler_y.transform(train[[f"{col}_Future"]]).ravel())
                    models[col] = model

Python
scaler_X_selected_dict.update(scaler_X_selected_dict_local): Добавляем новые скейлеры в глобальный словарь.

models = {}: Создаем пустой словарь для хранения финальных моделей.

for idx, col in enumerate(['High', 'Low', 'Close']):: Итерируемся по столбцам.

scaler_y = [scaler_y_high, scaler_y_low, scaler_y_close][idx]: Получаем соответствующий скейлер для целевой переменной.

X_train_selected = X_train_selected_dict[col]: Получаем масштабированные тренировочные данные для текущего столбца.

model = xgb.XGBRegressor(...): Создаём экземпляр XGBoost регрессора.

model.fit(X_train_selected, scaler_y.transform(train[[f"{col}_Future"]]).ravel()): Обучаем модель на отобранных признаках и целевой переменной.

models[col] = model: Сохраняем модель в словарь.

# Generate predictions only for Test period
                predictions_test = {}
                for idx, col in enumerate(['High', 'Low', 'Close']):
                    scaler_y = [scaler_y_high, scaler_y_low, scaler_y_close][idx]
                    X_test_selected = X_test_selected_dict[col]
                    preds_scaled = models[col].predict(X_test_selected)
                    preds_test = scaler_y.inverse_transform(preds_scaled.reshape(-1, 1)).flatten()
                    predictions_test[col] = preds_test

                # Generate predictions only for Backtest period
                predictions_backtest = {}
                for idx, col in enumerate(['High', 'Low', 'Close']):
                    scaler_y = [scaler_y_high, scaler_y_low, scaler_y_close][idx]
                    X_backtest_selected = X_backtest_selected_dict[col]
                    preds_scaled = models[col].predict(X_backtest_selected)
                    preds_backtest = scaler_y.inverse_transform(preds_scaled.reshape(-1, 1)).flatten()
                    predictions_backtest[col] = preds_backtest

Python
# Generate predictions only for Test period: Комментарий, что далее идет блок формирования предсказаний для тестовой выборки.

predictions_test = {}: Создаем пустой словарь для хранения предсказаний.

for idx, col in enumerate(['High', 'Low', 'Close']):: Итерируемся по столбцам.

scaler_y = [scaler_y_high, scaler_y_low, scaler_y_close][idx]: Получаем соответствующий скейлер для целевой переменной.

X_test_selected = X_test_selected_dict[col]: Получаем масштабированные тестовые данные.

preds_scaled = models[col].predict(X_test_selected): Получаем предсказания.

preds_test = scaler_y.inverse_transform(preds_scaled.reshape(-1, 1)).flatten(): Инвертируем масштабирование предсказаний.

predictions_test[col] = preds_test: Сохраняем предсказания в словарь.

# Generate predictions only for Backtest period: Комментарий, что далее идет блок формирования предсказаний для бэктестовой выборки.

predictions_backtest = {}: Создаем пустой словарь для хранения предсказаний.

for idx, col in enumerate(['High', 'Low', 'Close']):: Итерируемся по столбцам.

scaler_y = [scaler_y_high, scaler_y_low, scaler_y_close][idx]: Получаем соответствующий скейлер для целевой переменной.

X_backtest_selected = X_backtest_selected_dict[col]: Получаем масштабированные данные для бэктеста.

preds_scaled = models[col].predict(X_backtest_selected): Получаем предсказания.

preds_backtest = scaler_y.inverse_transform(preds_scaled.reshape(-1, 1)).flatten(): Инвертируем масштабирование предсказаний.

predictions_backtest[col] = preds_backtest: Сохраняем предсказания в словарь.

test['Predicted_High_Future'] = predictions_test['High']
                test['Predicted_Low_Future'] = predictions_test['Low']
                test['Predicted_Close_Future'] = predictions_test['Close']

                backtest['Predicted_High_Future'] = predictions_backtest['High']
                backtest['Predicted_Low_Future'] = predictions_backtest['Low']
                backtest['Predicted_Close_Future'] = predictions_backtest['Close']

Python
test['Predicted_High_Future'] = predictions_test['High']: Добавляем столбец с предсказанными значениями High_Future в DataFrame тестовой выборки.

test['Predicted_Low_Future'] = predictions_test['Low']: Добавляем столбец с предсказанными значениями Low_Future в DataFrame тестовой выборки.

test['Predicted_Close_Future'] = predictions_test['Close']: Добавляем столбец с предсказанными значениями Close_Future в DataFrame тестовой выборки.

backtest['Predicted_High_Future'] = predictions_backtest['High']: Добавляем столбец с предсказанными значениями High_Future в DataFrame бэктестовой выборки.

backtest['Predicted_Low_Future'] = predictions_backtest['Low']: Добавляем столбец с предсказанными значениями Low_Future в DataFrame бэктестовой выборки.

backtest['Predicted_Close_Future'] = predictions_backtest['Close']: Добавляем столбец с предсказанными значениями Close_Future в DataFrame бэктестовой выборки.

if show_plots and False:
                    plot_predicted_vs_actual(test, symbol, timeframe_name, 'Predicted_Close_Future',
                                             plot_dir_symbol_date, data_source='test')
                    plot_predicted_vs_actual(backtest, symbol, timeframe_name, 'Predicted_Close_Future',
                                             plot_dir_symbol_date, data_source='backtest')

Python
if show_plots and False:: Проверяем, нужно ли отрисовывать графики (здесь условие всегда false).

plot_predicted_vs_actual(test, symbol, timeframe_name, 'Predicted_Close_Future', plot_dir_symbol_date, data_source='test'): Отрисовываем график для тестовой выборки.

plot_predicted_vs_actual(backtest, symbol, timeframe_name, 'Predicted_Close_Future', plot_dir_symbol_date, data_source='backtest'): Отрисовываем график для бэктестовой выборки.

X_train_selected_close = X_train_selected_dict['Close']
                scores = cross_val_score(models['Close'], X_train_selected_close, y_train_close.ravel(),
                                         cv=50, scoring='neg_mean_squared_error', n_jobs=-1)

Python
X_train_selected_close = X_train_selected_dict['Close']: Получаем масштабированные тренировочные данные для цены Close.

scores = cross_val_score(...): Проводим кросс-валидацию модели, используя 50 фолдов и MSE в качестве метрики.

estimators_close = [
                    ('xgb', xgb.XGBRegressor(
                        objective='reg:squarederror',
                        n_estimators=xgb_n_estimators,
                        max_depth=xgb_max_depth,
                        learning_rate=xgb_learning_rate,
                        random_state=xgb_random_state,
                        n_jobs=-1
                    )),
                    ('rf', RandomForestRegressor(n_estimators=100, random_state=xgb_random_state, n_jobs=-1)),
                    ('gb', GradientBoostingRegressor(n_estimators=100, random_state=xgb_random_state)),
                    ('lr', LinearRegression())
                ]

Python
estimators_close = [ ... ]: Создаём список эстиматоров (алгоритмов) для стекинга.

stacking_regressor_close = StackingRegressor(
                    estimators=estimators_close,
                    final_estimator=LinearRegression(),
                    n_jobs=-1
                )

                stacking_regressor_close.fit(X_train_selected_dict['Close'], y_train_close.ravel())
                predictions_scaled_close = stacking_regressor_close.predict(X_test_selected_dict['Close'])
                predictions_close = scaler_y_close.inverse_transform(
                    predictions_scaled_close.reshape(-1, 1)).ravel()

                predictions_scaled_close_backtest = stacking_regressor_close.predict(X_backtest_selected_dict['Close'])
                predictions_close_backtest = scaler_y_close.inverse_transform(
                    predictions_scaled_close_backtest.reshape(-1, 1)).ravel()

Python
stacking_regressor_close = StackingRegressor(...): Создаём экземпляр StackingRegressor.

stacking_regressor_close.fit(X_train_selected_dict['Close'], y_train_close.ravel()): Обучаем модель стекинга.

predictions_scaled_close = stacking_regressor_close.predict(X_test_selected_dict['Close']): Получаем предсказания модели стекинга для тестовой выборки.

predictions_close = scaler_y_close.inverse_transform(predictions_scaled_close.reshape(-1, 1)).ravel(): Инвертируем масштабирование предсказаний для тестовой выборки.

predictions_scaled_close_backtest = stacking_regressor_close.predict(X_backtest_selected_dict['Close']): Получаем предсказания модели стекинга для бэктестовой выборки.

predictions_close_backtest = scaler_y_close.inverse_transform(predictions_scaled_close_backtest.reshape(-1, 1)).ravel(): Инвертируем масштабирование предсказаний для бэктестовой выборки.

test['Ensemble_Predicted_Close_Future'] = predictions_close
                backtest['Ensemble_Predicted_Close_Future'] = predictions_close_backtest

Python
test['Ensemble_Predicted_Close_Future'] = predictions_close: Добавляем столбец с предсказанными значениями для тестовой выборки.

backtest['Ensemble_Predicted_Close_Future'] = predictions_close_backtest: Добавляем столбец с предсказанными значениями для бэктестовой выборки.

if show_plots and False:
                    plot_error_distribution(test, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                            plot_dir_symbol_date, data_source='test')
                    plot_residuals(test, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                   plot_dir_symbol_date, data_source='test')

                    plot_error_distribution(backtest, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                            plot_dir_symbol_date, data_source='backtest')
                    plot_residuals(backtest, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                   plot_dir_symbol_date, data_source='backtest')

Python
if show_plots and False:: Проверяем, нужно ли отрисовывать графики (здесь условие всегда false).

plot_error_distribution(...): Отрисовываем распределение ошибок для тестовой выборки.

plot_residuals(...): Отрисовываем остатки для тестовой выборки.

plot_error_distribution(...): Отрисовываем распределение ошибок для бэктестовой выборки.

plot_residuals(...): Отрисовываем остатки для бэктестовой выборки.

# Change made here: Pass test and backtest directly without .copy()
                evaluator_ensemble_close_test = StrategyEvaluator(
                    symbol=symbol,
                    df=test,  # Changed from test.copy() to test
                    train=train.copy(),
                    models=models,
                    scaler_y_high=scaler_y_high,
                    scaler_y_low=scaler_y_low,
                    scaler_y_close=scaler_y_close,
                    prediction_columns=['Ensemble_Predicted_Close_Future']
                )

Python
evaluator_ensemble_close_test = StrategyEvaluator(...): Создаем экземпляр StrategyEvaluator для оценки стратегии на тестовой выборке.

Обратите внимание, что теперь тестовые данные передаются без копирования.

# Define default parameters before using
                window_default = 20
                multiplier_default = 1.0
                tp_factor_default = 2.0
                sl_factor_default = 0.5

                # Evaluate Strategy on Test Data
                evaluator_ensemble_close_test.evaluate(
                    [window_default, multiplier_default, tp_factor_default, sl_factor_default],
                    classifier_sl=None, sl_threshold=0.7, backtest_data=None, data_source='test'  # Ensure data_source is passed
                )

Python
window_default = 20: Устанавливаем значение окна по умолчанию.

multiplier_default = 1.0: Устанавливаем значение мультипликатора по умолчанию.

tp_factor_default = 2.0: Устанавливаем значение фактора тейк-профита по умолчанию.

sl_factor_default = 0.5: Устанавливаем значение фактора стоп-лосса по умолчанию.

evaluator_ensemble_close_test.evaluate(...): Оцениваем стратегию на тестовой выборке с параметрами по умолчанию и без использования SL.

evaluator_ensemble_close = StrategyEvaluator(
                    symbol=symbol,
                    df=backtest,  # Changed from backtest.copy() to backtest
                    train=train.copy(),
                    models=models,
                    scaler_y_high=scaler_y_high,
                    scaler_y_low=scaler_y_low,
                    scaler_y_close=scaler_y_close,
                    prediction_columns=['Ensemble_Predicted_Close_Future']
                )

Python
evaluator_ensemble_close = StrategyEvaluator(...): Создаем экземпляр StrategyEvaluator для оценки стратегии на бэктестовой выборке.

Обратите внимание, что теперь данные для бэктеста передаются без копирования.

# Evaluate Strategy on Backtest Data
                evaluator_ensemble_close.evaluate(
                    [window_default, multiplier_default, tp_factor_default, sl_factor_default],
                    classifier_sl=None, sl_threshold=0.7, backtest_data=backtest, data_source='backtest'  # Ensure data_source is passed
                )

Python
evaluator_ensemble_close.evaluate(...): Оцениваем стратегию на бэктестовой выборке с параметрами по умолчанию и без использования SL.

df_trades = pd.DataFrame(evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future'])

Python
df_trades = pd.DataFrame(evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future']): Получаем DataFrame с информацией о сделках на бэктесте.

if df_trades.empty:
                    # Try fallback parameters
                    fallback_window = 10
                    fallback_multiplier = 0.5
                    fallback_tp_factor = 1.0
                    fallback_sl_factor = 0.2

                    evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future'].clear()
                    evaluator_ensemble_close.evaluate(
                        [fallback_window, fallback_multiplier, fallback_tp_factor, fallback_sl_factor],
                        classifier_sl=None, sl_threshold=0.7, backtest_data=backtest, data_source='backtest'  # Ensure data_source is passed
                    )

                    df_trades = pd.DataFrame(evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future'])
                    if df_trades.empty:
                        logging.warning("No trades even with fallback parameters.")
                        continue

Python
if df_trades.empty:: Проверяем, есть ли сделки в DataFrame.

fallback_window = 10: Устанавливаем запасное значение окна.

fallback_multiplier = 0.5: Устанавливаем запасное значение мультипликатора.

fallback_tp_factor = 1.0: Устанавливаем запасное значение фактора тейк-профита.

fallback_sl_factor = 0.2: Устанавливаем запасное значение фактора стоп-лосса.

evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future'].clear(): Очищаем список сделок

evaluator_ensemble_close.evaluate(...): Оцениваем стратегию на бэктестовой выборке с запасными параметрами.

df_trades = pd.DataFrame(evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future']): Получаем DataFrame со сделками на бэктесте.

if df_trades.empty:: Проверяем, есть ли сделки в DataFrame.

logging.warning("No trades even with fallback parameters."): Выводим сообщение о том, что даже с запасными параметрами сделок нет.

continue: Переходим к следующей паре.

if df_trades.empty:: Проверяем, есть ли сделки в DataFrame.

logging.warning("No trades to train SL model."): Выводим сообщение о том, что нет сделок для обучения SL-модели.

continue: Переходим к следующей паре.

trade_entries = []
                for trade in evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future']:
                    entry_date = trade['Entry_Date']
                    hit_sl = trade['Hit_SL']
                    if entry_date in train.index:
                        entry_features = train.loc[entry_date, sl_features].to_dict()
                    elif entry_date in test.index:
                        entry_features = test.loc[entry_date, sl_features].to_dict()
                    elif entry_date in backtest.index:
                        entry_features = backtest.loc[entry_date, sl_features].to_dict()
                    else:
                        continue

                    trade_entries.append({
                        'Hit_SL': hit_sl,
                        **entry_features
                    })

Python
trade_entries = []: Создаём пустой список для хранения данных о сделках.

for trade in evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future']:: Итерируемся по сделкам из evaluator_ensemble_close.trades_dict.

entry_date = trade['Entry_Date']: Получаем дату открытия сделки.

hit_sl = trade['Hit_SL']: Получаем флаг срабатывания SL.

if entry_date in train.index:: Если дата в обучающем наборе.

entry_features = train.loc[entry_date, sl_features].to_dict(): Получаем features из обучающих данных по дате входа.

elif entry_date in test.index:: Если дата в тестовом наборе.
* entry_features = test.loc[entry_date, sl_features].to_dict(): Получаем features из тестовых данных по дате входа.

elif entry_date in backtest.index:: Если дата в бэктесте.

entry_features = backtest.loc[entry_date, sl_features].to_dict(): Получаем features из бэктестовых данных по дате входа.

else: continue: Пропускаем, если дата не найдена.

trade_entries.append({'Hit_SL': hit_sl, **entry_features}): Добавляем данные по сделки и её features в список.

df_trade_entries = pd.DataFrame(trade_entries)
                if df_trade_entries.empty:
                    logging.warning("No valid trade entries for SL model.")
                    continue

                df_trade_entries.fillna(method='ffill', inplace=True)
                df_trade_entries.fillna(method='bfill', inplace=True)

                X_sl = df_trade_entries[sl_features].values
                y_sl = df_trade_entries['Hit_SL'].values

                scaler_sl = MinMaxScaler()
                X_sl_scaled = scaler_sl.fit_transform(X_sl)

Python
df_trade_entries = pd.DataFrame(trade_entries): Создаем DataFrame из данных по сделкам.

if df_trade_entries.empty:: Проверяем, есть ли записи для обучения SL-модели.

logging.warning("No valid trade entries for SL model."): Если нет, то выводим предупреждение и переходим к следующей паре.

continue: Пропускаем.

df_trade_entries.fillna(method='ffill', inplace=True): Заполняем NaN значения.

df_trade_entries.fillna(method='bfill', inplace=True): Заполняем NaN значения.

X_sl = df_trade_entries[sl_features].values: Получаем значения признаков для SL-модели.

y_sl = df_trade_entries['Hit_SL'].values: Получаем целевую переменную

80.9s
Продолжайте отвечать, включая последнюю строку ответа.

Хорошо, продолжаем разбор кода с последней строки предыдущего ответа:

scaler_sl = MinMaxScaler()
                X_sl_scaled = scaler_sl.fit_transform(X_sl)

                sl_max_depth_default = 5
                sl_learning_rate_default = 0.1
                sl_n_estimators_default = 100

                try:
                    X_train_sl, X_val_sl, y_train_sl, y_val_sl = train_test_split(
                        X_sl_scaled, y_sl, test_size=0.2, random_state=ga_base_seed, stratify=y_sl
                    )
                except ValueError:
                    X_train_sl, X_val_sl, y_train_sl, y_val_sl = train_test_split(
                        X_sl_scaled, y_sl, test_size=0.2, random_state=ga_base_seed
                    )

                classifier_sl = xgb.XGBClassifier(
                    objective='binary:logistic',
                    n_estimators=int(sl_n_estimators_default),
                    max_depth=int(sl_max_depth_default),
                    learning_rate=sl_learning_rate_default,
                    random_state=xgb_random_state,
                    use_label_encoder=False,
                    eval_metric='logloss',
                    n_jobs=-1
                )

Python
scaler_sl = MinMaxScaler(): Создаём экземпляр MinMaxScaler для масштабирования данных SL модели.

X_sl_scaled = scaler_sl.fit_transform(X_sl): Масштабируем фичи для SL-модели.

sl_max_depth_default = 5: Устанавливаем значение максимальной глубины дерева для SL-модели по умолчанию.

sl_learning_rate_default = 0.1: Устанавливаем значение скорости обучения для SL-модели по умолчанию.

sl_n_estimators_default = 100: Устанавливаем значение количества деревьев для SL-модели по умолчанию.

try ... except ValueError:: Обработка возможной ошибки, если не получится стратифицировать выборку.

X_train_sl, X_val_sl, y_train_sl, y_val_sl = train_test_split(X_sl_scaled, y_sl, test_size=0.2, random_state=ga_base_seed, stratify=y_sl): Разделяем данные на обучающую и валидационную выборку со стратификацией.

except ValueError:: Если стратификация невозможна.

X_train_sl, X_val_sl, y_train_sl, y_val_sl = train_test_split(X_sl_scaled, y_sl, test_size=0.2, random_state=ga_base_seed): Разделяем данные на обучающую и валидационную выборку без стратификации.

classifier_sl = xgb.XGBClassifier(...): Создаём экземпляр XGBoost классификатора для SL-модели.

classifier_sl.fit(X_train_sl, y_train_sl)
                joblib.dump(classifier_sl, 'sl_prediction_model.joblib')
                joblib.dump(scaler_sl, 'sl_scaler.joblib')

                scaler_X_selected_dict['Close_SL'] = scaler_sl

                if show_plots:
                    plot_sl_features_shap(
                        classifier_sl,
                        X_sl_scaled,
                        sl_features,
                        symbol,
                        timeframe_name,
                        plot_dir_symbol_date,
                        data_source='test'
                    )

Python
classifier_sl.fit(X_train_sl, y_train_sl): Обучаем SL-модель.

joblib.dump(classifier_sl, 'sl_prediction_model.joblib'): Сохраняем обученную SL-модель.

joblib.dump(scaler_sl, 'sl_scaler.joblib'): Сохраняем скейлер.

scaler_X_selected_dict['Close_SL'] = scaler_sl: Добавляем скейлер в словарь.

if show_plots:: Проверяем, включена ли отрисовка графиков.

plot_sl_features_shap(...): Отрисовываем SHAP summary plot для SL-модели.

toolbox_ga = create_toolbox(evaluator_instance=evaluator_ensemble_close,
                                            sl_features=sl_features,
                                            ga_base_seed=ga_base_seed)

                best_individual = run_ga(toolbox_ga, ga_num_generations, ga_pop_size,
                                         f"Ensemble Predictions Close - GA Optimization", patience=10,
                                         backtest_data=backtest, data_source='backtest')  # Added data_source='backtest'

Python
toolbox_ga = create_toolbox(...): Создаём toolbox для генетического алгоритма.

best_individual = run_ga(...): Запускаем генетический алгоритм и получаем лучшего индивида.

Обратите внимание, что теперь в функцию run_ga передаётся источник данных.

if best_individual is not None:
                    best_params = [float(x) for x in best_individual]
                    logging.info(f"Best GA Individual: {best_params}")

                    evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future'].clear()
                    evaluator_ensemble_close.evaluate(
                        [int(best_params[0]), best_params[1], best_params[2], best_params[3]],
                        classifier_sl=None, sl_threshold=0.7, backtest_data=backtest, data_source='backtest'  # Ensure data_source is passed
                    )

Python
if best_individual is not None:: Проверяем, получен ли лучший индивид.

best_params = [float(x) for x in best_individual]: Конвертируем параметры лучшего индивида в float.

logging.info(f"Best GA Individual: {best_params}"): Выводим информацию о лучших параметрах.

evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future'].clear(): Очищаем список сделок

evaluator_ensemble_close.evaluate(...): Оцениваем стратегию на бэктестовых данных без SL, используя параметры, найденные генетическим алгоритмом.

Обратите внимание, что теперь в функцию evaluate передаётся источник данных.

trade_entries_best = []
                    for trade in evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future']:
                        entry_date = trade['Entry_Date']
                        hit_sl = trade['Hit_SL']
                        try:
                            entry_features = evaluator_ensemble_close.df.loc[entry_date, sl_features].to_dict()
                            trade_entries_best.append({'Hit_SL': hit_sl, **entry_features})
                        except KeyError:
                            continue

Python
trade_entries_best = []: Создаем пустой список для хранения информации по сделкам, которые открылись на лучших параметрах.

for trade in evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future']:: Итерируемся по сделкам.

entry_date = trade['Entry_Date']: Получаем дату входа в сделку.

hit_sl = trade['Hit_SL']: Получаем флаг срабатывания стоп-лосса.

try ... except KeyError:: Обработка ошибок KeyError, возникающих при обращении к DataFrame.

entry_features = evaluator_ensemble_close.df.loc[entry_date, sl_features].to_dict(): Получаем фичи для SL-модели.

trade_entries_best.append({'Hit_SL': hit_sl, **entry_features}): Добавляем данные по сделки и её фичи в список.

df_trade_entries_best = pd.DataFrame(trade_entries_best)

                    if not df_trade_entries_best.empty:
                        df_trade_entries_best.fillna(method='ffill', inplace=True)
                        df_trade_entries_best.fillna(method='bfill', inplace=True)

                        X_sl_best = df_trade_entries_best[sl_features].values
                        y_sl_best = df_trade_entries_best['Hit_SL'].values

                        scaler_sl_best = MinMaxScaler()
                        X_sl_scaled_best = scaler_sl_best.fit_transform(X_sl_best)

                        classifier_sl_best = xgb.XGBClassifier(
                            objective='binary:logistic',
                            n_estimators=int(best_params[6]),
                            max_depth=int(best_params[4]),
                            learning_rate=best_params[5],
                            random_state=xgb_random_state,
                            use_label_encoder=False,
                            eval_metric='logloss',
                            n_jobs=-1
                        )
                        classifier_sl_best.fit(X_sl_scaled_best, y_sl_best)

                        scaler_X_selected_dict['Close_SL'] = scaler_sl_best
                        joblib.dump(classifier_sl_best, 'sl_prediction_model_best.joblib')
                        joblib.dump(scaler_sl_best, 'sl_scaler_best.joblib')

                        evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future'].clear()
                        evaluator_ensemble_close.evaluate(
                            [int(best_params[0]), best_params[1], best_params[2], best_params[3]],
                            classifier_sl=classifier_sl_best, sl_threshold=0.7, backtest_data=backtest, data_source='backtest'  # Ensure data_source is passed
                        )

                        logging.info("Best GA Individual Strategy Evaluation Complete.")
                    else:
                        logging.warning("No trade entries found for the best GA individual.")
                        continue

Python
df_trade_entries_best = pd.DataFrame(trade_entries_best): Создаем DataFrame с информацией о сделках, полученных на лучших параметрах.

if not df_trade_entries_best.empty:: Проверяем, есть ли сделки для обучения SL-модели на лучших параметрах.

df_trade_entries_best.fillna(method='ffill', inplace=True): Заполняем NaN значения.

df_trade_entries_best.fillna(method='bfill', inplace=True): Заполняем NaN значения.

X_sl_best = df_trade_entries_best[sl_features].values: Получаем фичи для SL-модели.

y_sl_best = df_trade_entries_best['Hit_SL'].values: Получаем целевую переменную для SL-модели.

scaler_sl_best = MinMaxScaler(): Создаем экземпляр скейлера.

X_sl_scaled_best = scaler_sl_best.fit_transform(X_sl_best): Масштабируем данные.

classifier_sl_best = xgb.XGBClassifier(...): Создаем экземпляр XGBoost классификатора для SL-модели.

classifier_sl_best.fit(X_sl_scaled_best, y_sl_best): Обучаем SL-модель.

scaler_X_selected_dict['Close_SL'] = scaler_sl_best: Сохраняем скейлер.

joblib.dump(classifier_sl_best, 'sl_prediction_model_best.joblib'): Сохраняем обученную SL-модель.

joblib.dump(scaler_sl_best, 'sl_scaler_best.joblib'): Сохраняем скейлер.

evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future'].clear(): Очищаем список сделок.
* evaluator_ensemble_close.evaluate(...): Оцениваем стратегию с SL-моделью, используя параметры, найденные генетическим алгоритмом.

Обратите внимание, что теперь в функцию evaluate передаётся источник данных.
* logging.info("Best GA Individual Strategy Evaluation Complete."): Выводим сообщение о том, что оценка стратегии на лучших параметрах завершена.

else:: Если сделок нет

logging.warning("No trade entries found for the best GA individual."): Выводим предупреждение о том что не были найдены сделки для лучшего индивида.

continue: Переходим к следующей паре.

else:: Если лучший индивид не был найден.

logging.warning(f"No valid GA individual found for {symbol} ({timeframe_name}). Skipping."): Выводим предупреждение о том, что не найден лучший индивид.

continue: Переходим к следующей паре.

if show_plots:
                    plot_error_distribution(test, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                            plot_dir_symbol_date, data_source='test')
                    plot_residuals(test, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                   plot_dir_symbol_date, data_source='test')

                    backtest_strategy(evaluator_ensemble_close_test.df, symbol, timeframe_name,
                                      'Ensemble_Predicted_Close_Future',
                                      'Ensemble_Predicted_Close_Future', plot_dir_symbol_date, data_source='test')
                    trades_list_test = evaluator_ensemble_close_test.trades_dict['Ensemble_Predicted_Close_Future']
                    plot_trade_metrics(trades_list_test, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                       plot_dir_symbol_date, data_source='test')
                    plot_trade_execution(evaluator_ensemble_close_test.df, trades_list_test, symbol, timeframe_name,
                                         'Ensemble_Predicted_Close_Future',
                                         plot_dir_symbol_date, data_source='test')
                    plot_predicted_vs_actual(evaluator_ensemble_close_test.df, symbol, timeframe_name,
                                             'Ensemble_Predicted_Close_Future', plot_dir_symbol_date,
                                             data_source='test')

                    plot_error_distribution(backtest, symbol, timeframe_name,
                                            'Ensemble_Predicted_Close_Future',
                                            plot_dir_symbol_date, data_source='backtest')
                    plot_residuals(backtest, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                   plot_dir_symbol_date, data_source='backtest')
                    backtest_strategy(backtest, symbol, timeframe_name,
                                      'Ensemble_Predicted_Close_Future',
                                      'Ensemble_Predicted_Close_Future', plot_dir_symbol_date, data_source='backtest')
                    trades_list = evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future']
                    plot_trade_metrics(trades_list, symbol, timeframe_name, 'Ensemble_Predicted_Close_Future',
                                       plot_dir_symbol_date, data_source='backtest')
                    plot_trade_execution(backtest, trades_list, symbol, timeframe_name,
                                         'Ensemble_Predicted_Close_Future',
                                         plot_dir_symbol_date, data_source='backtest')
                    plot_predicted_vs_actual(backtest, symbol, timeframe_name,
                                             'Ensemble_Predicted_Close_Future', plot_dir_symbol_date,
                                             data_source='backtest')

Python
if show_plots:: Проверяем, включена ли отрисовка графиков.

plot_error_distribution(...): Отрисовываем распределение ошибок для тестовой выборки.

plot_residuals(...): Отрисовываем остатки для тестовой выборки.

backtest_strategy(...): Отрисовываем кумулятивную доходность для тестовой выборки.

trades_list_test = evaluator_ensemble_close_test.trades_dict['Ensemble_Predicted_Close_Future']: Получаем список сделок для тестовой выборки.

plot_trade_metrics(...): Отрисовываем метрики торговли для тестовой выборки.

plot_trade_execution(...): Отрисовываем точки закрытия позиций на графике цены для тестовой выборки.

plot_predicted_vs_actual(...): Отрисовываем график с фактической и предсказанной ценой для тестовой выборки.

plot_error_distribution(...): Отрисовываем распределение ошибок для бэктестовой выборки.

plot_residuals(...): Отрисовываем остатки для бэктестовой выборки.

backtest_strategy(...): Отрисовываем кумулятивную доходность для бэктестовой выборки.

trades_list = evaluator_ensemble_close.trades_dict['Ensemble_Predicted_Close_Future']: Получаем список сделок для бэктестовой выборки.

plot_trade_metrics(...): Отрисовываем метрики торговли для бэктестовой выборки.

plot_trade_execution(...): Отрисовываем точки закрытия позиций на графике цены для бэктестовой выборки.

plot_predicted_vs_actual(...): Отрисовываем график с фактической и предсказанной ценой для бэктестовой выборки.

mse_ensemble = mean_squared_error(backtest['Close_Future'],
                                                  backtest['Ensemble_Predicted_Close_Future'])
                mae_ensemble = mean_absolute_error(backtest['Close_Future'],
                                                   backtest['Ensemble_Predicted_Close_Future'])
                r2_ensemble = r2_score(backtest['Close_Future'],
                                       backtest['Ensemble_Predicted_Close_Future'])
                mape_ensemble = mean_absolute_percentage_error(backtest['Close_Future'],
                                                                backtest['Ensemble_Predicted_Close_Future'])

                logging.info(f"Ensemble Model Performance for {symbol} ({timeframe_name}):")
                logging.info(f"MSE: {mse_ensemble:.4f}")
                logging.info(f"MAE: {mae_ensemble:.4f}")
                logging.info(f"R² Score: {r2_ensemble:.4f}")
                logging.info(f"MAPE: {mape_ensemble:.4f}")

                del df, train, test, backtest, models, predictions_test, predictions_backtest
                gc.collect()

            gc.collect()

Python
mse_ensemble = mean_squared_error(...): Вычисляем MSE для бэктестовой выборки.

mae_ensemble = mean_absolute_error(...): Вычисляем MAE для бэктестовой выборки.

r2_ensemble = r2_score(...): Вычисляем R² для бэктестовой выборки.

mape_ensemble = mean_absolute_percentage_error(...): Вычисляем MAPE для бэктестовой выборки.

logging.info(f"Ensemble Model Performance for {symbol} ({timeframe_name}):"): Выводим сообщение.

logging.info(f"MSE: {mse_ensemble:.4f}"): Выводим сообщение с MSE.

logging.info(f"MAE: {mae_ensemble:.4f}"): Выводим сообщение с MAE.

logging.info(f"R² Score: {r2_ensemble:.4f}"): Выводим сообщение с R².

logging.info(f"MAPE: {mape_ensemble:.4f}"): Выводим сообщение с MAPE.

del df, train, test, backtest, models, predictions_test, predictions_backtest: Удаляем переменные из памяти.

gc.collect(): Вызываем сборщик мусора.

gc.collect(): Вызываем сборщик мусора еще раз, на всякий случай.

gc.collect()

if __name__ == '__main__':
    try:
        main_function()
    finally:
        logging.info("MT5 shutdown successfully.")
        print("MT5 shutdown successfully.")
        mt5.shutdown()

Python
gc.collect(): Вызываем сборщик мусора.

if __name__ == '__main__':: Проверяем, что скрипт запущен как основная программа.

try ... finally:: Обработка возможных исключений.

main_function(): Запускаем основную функцию.

finally:: Блок, выполняемый всегда в конце работы скрипта.

logging.info("MT5 shutdown successfully."): Выводим сообщение об успешном завершении работы.

print("MT5 shutdown successfully."): Выводим сообщение об успешном завершении работы.

mt5.shutdown(): Завершаем работу MT5.